diff --git a/app/dialog/sequence/sequence.cpp b/app/dialog/sequence/sequence.cpp index ce15018e8..c999fd297 100644 --- a/app/dialog/sequence/sequence.cpp +++ b/app/dialog/sequence/sequence.cpp @@ -136,7 +136,7 @@ void SequenceDialog::accept() sequence_->SetVideoParams(video_params); sequence_->SetAudioParams(audio_params); sequence_->SetLabel(name_field_->text()); - sequence_->video_frame_cache()->SetEnabled(parameter_tab_->GetSelectedPreviewAutoCache()); + sequence_->video_frame_cache()->SetIsAutomatic(parameter_tab_->GetSelectedPreviewAutoCache()); } QDialog::accept(); @@ -170,7 +170,7 @@ SequenceDialog::SequenceParamCommand::SequenceParamCommand(Sequence* s, old_video_params_(s->GetVideoParams()), old_audio_params_(s->GetAudioParams()), old_name_(s->GetLabel()), - old_autocache_(s->video_frame_cache()->IsEnabled()) + old_autocache_(s->video_frame_cache()->IsAutomatic()) { } @@ -188,7 +188,7 @@ void SequenceDialog::SequenceParamCommand::redo() sequence_->SetAudioParams(new_audio_params_); } sequence_->SetLabel(new_name_); - sequence_->video_frame_cache()->SetEnabled(new_autocache_); + sequence_->video_frame_cache()->SetIsAutomatic(new_autocache_); } void SequenceDialog::SequenceParamCommand::undo() @@ -200,7 +200,7 @@ void SequenceDialog::SequenceParamCommand::undo() sequence_->SetAudioParams(old_audio_params_); } sequence_->SetLabel(old_name_); - sequence_->video_frame_cache()->SetEnabled(old_autocache_); + sequence_->video_frame_cache()->SetIsAutomatic(old_autocache_); } } diff --git a/app/dialog/sequence/sequencedialogparametertab.cpp b/app/dialog/sequence/sequencedialogparametertab.cpp index 73eff0f1e..9e39aa005 100644 --- a/app/dialog/sequence/sequencedialogparametertab.cpp +++ b/app/dialog/sequence/sequencedialogparametertab.cpp @@ -89,7 +89,7 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg interlacing_combo_->SetInterlaceMode(vp.interlacing()); preview_resolution_field_->SetDivider(vp.divider()); preview_format_field_->SetPixelFormat(vp.format()); - preview_autocache_field_->setChecked(sequence->video_frame_cache()->IsEnabled()); + preview_autocache_field_->setChecked(sequence->video_frame_cache()->IsAutomatic()); audio_sample_rate_field_->SetSampleRate(ap.sample_rate()); audio_channels_field_->SetChannelLayout(ap.channel_layout()); diff --git a/app/node/block/block.h b/app/node/block/block.h index 3a65159e2..2ad077ee5 100644 --- a/app/node/block/block.h +++ b/app/node/block/block.h @@ -96,6 +96,7 @@ public: void set_track(Track* track) { track_ = track; + emit TrackChanged(track_); } bool is_enabled() const; @@ -126,6 +127,8 @@ signals: void PreviewChanged(); + void TrackChanged(Track *track); + protected: virtual void InputValueChangedEvent(const QString& input, int element) override; diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 241484965..b603d4af7 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -105,13 +105,7 @@ void ClipBlock::set_length_and_media_in(const rational &length) if (!reverse()) { // Calculate media_in adjustment rational proposed_media_in = SequenceToMediaTime(this->length() - length, false, true); - - waveform_.TrimIn(proposed_media_in - media_in()); - set_media_in(proposed_media_in); - } else { - // Trim waveform out point - waveform_.TrimIn(this->length() - length); } super::set_length_and_media_in(length); @@ -187,6 +181,19 @@ void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int // If signal is from texture input, transform all times from media time to sequence time if (from == kBufferIn) { + Track::Type type = GetTrackType(); + + if (type == Track::kVideo || type == Track::kAudio) { + if (Node *connected = GetConnectedOutput(from, element)) { + TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, length())); + if (type == Track::kVideo) { + emit connected->video_frame_cache()->Request(range.Intersected(max_range), true); + } else if (type == Track::kAudio) { + emit connected->audio_playback_cache()->Request(range.Intersected(max_range), true); + } + } + } + // Adjust range from media time to sequence time TimeRange adj; double speed_value = speed(); @@ -238,6 +245,24 @@ void ClipBlock::LinkChangeEvent() } } +void ClipBlock::InputConnectedEvent(const QString &input, int element, Node *output) +{ + super::InputConnectedEvent(input, element, output); + + if (input == kBufferIn) { + connect(output->audio_playback_cache(), &AudioPlaybackCache::WaveformUpdated, this, &Block::PreviewChanged); + } +} + +void ClipBlock::InputDisconnectedEvent(const QString &input, int element, Node *output) +{ + super::InputDisconnectedEvent(input, element, output); + + if (input == kBufferIn) { + disconnect(output->audio_playback_cache(), &AudioPlaybackCache::WaveformUpdated, this, &Block::PreviewChanged); + } +} + TimeRange ClipBlock::InputTimeAdjustment(const QString& input, int element, const TimeRange& input_time) const { Q_UNUSED(element) diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index fce20dbbd..dd075b728 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -23,6 +23,7 @@ #include "audio/audiovisualwaveform.h" #include "node/block/block.h" +#include "node/output/track/track.h" namespace olive { @@ -46,6 +47,15 @@ public: virtual void set_length_and_media_out(const rational &length) override; virtual void set_length_and_media_in(const rational &length) override; + Track::Type GetTrackType() const + { + if (track()) { + return track()->type(); + } else { + return Track::kNone; + } + } + rational media_in() const; void set_media_in(const rational& media_in); @@ -109,9 +119,19 @@ public: return block_links_; } - AudioVisualWaveform& waveform() + const AudioVisualWaveform *waveform() { - return waveform_; + if (Node *n = GetConnectedOutput(kBufferIn)) { + return &n->audio_playback_cache()->visual(); + } else { + return nullptr; + } + } + + void set_waveform(const AudioVisualWaveform *w) + { + qDebug() << "WAVEFORM COPY STUB"; + //audio_playback_cache()->set_visual(w); } ViewerOutput *connected_viewer() const @@ -119,6 +139,16 @@ public: return connected_viewer_; } + virtual TimeRange GetVideoCacheRange() const override + { + return TimeRange(0, length()); + } + + virtual TimeRange GetAudioCacheRange() const override + { + return TimeRange(0, length()); + } + static const QString kBufferIn; static const QString kMediaInInput; static const QString kSpeedInput; @@ -128,6 +158,10 @@ public: protected: virtual void LinkChangeEvent() override; + virtual void InputConnectedEvent(const QString& input, int element, Node *output) override; + + virtual void InputDisconnectedEvent(const QString& input, int element, Node *output) override; + private: rational SequenceToMediaTime(const rational& sequence_time, bool ignore_reverse = false, bool ignore_speed = false) const; @@ -141,11 +175,8 @@ private: ViewerOutput *connected_viewer_; private: - AudioVisualWaveform waveform_; - rational last_media_in_; - }; } diff --git a/app/node/node.cpp b/app/node/node.cpp index 143f1e9ee..b931cf504 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -933,11 +933,13 @@ void Node::InvalidateCache(const TimeRange &range, const QString &from, int elem Q_UNUSED(element) if (range.in() != range.out()) { - if (video_cache_->IsEnabled()) { - video_frame_cache()->Invalidate(range); + TimeRange vr = range.Intersected(GetVideoCacheRange()); + if (vr.length() != 0) { + video_frame_cache()->Invalidate(vr); } - if (audio_cache_->IsEnabled()) { - audio_playback_cache()->Invalidate(range); + TimeRange ar = range.Intersected(GetAudioCacheRange()); + if (ar.length() != 0) { + audio_playback_cache()->Invalidate(ar); } } diff --git a/app/node/node.h b/app/node/node.h index d123c0e72..dd38040d4 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -231,6 +231,9 @@ public: return audio_cache_; } + virtual TimeRange GetVideoCacheRange() const { return TimeRange(); } + virtual TimeRange GetAudioCacheRange() const { return TimeRange(); } + struct Position { Position(const QPointF &p = QPointF(0, 0), bool e = false) diff --git a/app/node/output/viewer/viewer.h b/app/node/output/viewer/viewer.h index b0ba3ec8c..2532fa3ae 100644 --- a/app/node/output/viewer/viewer.h +++ b/app/node/output/viewer/viewer.h @@ -147,6 +147,16 @@ public: return timeline_points_; } + virtual TimeRange GetVideoCacheRange() const override + { + return TimeRange(0, GetVideoLength()); + } + + virtual TimeRange GetAudioCacheRange() const override + { + return TimeRange(0, GetAudioLength()); + } + QVector GetEnabledStreamsAsReferences() const; QVector GetEnabledVideoStreams() const; diff --git a/app/render/audioplaybackcache.cpp b/app/render/audioplaybackcache.cpp index 859ce454e..d02311cef 100644 --- a/app/render/audioplaybackcache.cpp +++ b/app/render/audioplaybackcache.cpp @@ -161,6 +161,10 @@ void AudioPlaybackCache::WriteWaveform(const TimeRange &range, const TimeRangeLi visual_.OverwriteSilence(r.in(), r.length()); } } + + if (!valid_ranges.isEmpty()) { + emit WaveformUpdated(); + } } void AudioPlaybackCache::WriteSilence(const TimeRange &range) @@ -170,6 +174,11 @@ void AudioPlaybackCache::WriteSilence(const TimeRange &range) WritePCM(range, {range}, SampleBuffer()); } +void AudioPlaybackCache::TrimIn(const rational &in) +{ + visual_.TrimIn(in); +} + AudioPlaybackCache::Segment AudioPlaybackCache::CloneSegment(const AudioPlaybackCache::Segment &s) const { Segment new_seg = s; diff --git a/app/render/audioplaybackcache.h b/app/render/audioplaybackcache.h index 164953418..984135913 100644 --- a/app/render/audioplaybackcache.h +++ b/app/render/audioplaybackcache.h @@ -72,6 +72,8 @@ public: void WriteSilence(const TimeRange &range); + void TrimIn(const rational &in); + class Segment { public: @@ -200,14 +202,14 @@ public: */ PlaybackDevice* CreatePlaybackDevice(QObject *parent = nullptr) const; - const AudioVisualWaveform &visual() const - { - return visual_; - } + const AudioVisualWaveform &visual() const { return visual_; } + void set_visual(const AudioVisualWaveform &v) { visual_ = v; } signals: void ParametersChanged(); + void WaveformUpdated(); + private: static const qint64 kDefaultSegmentSizePerChannel; diff --git a/app/render/playbackcache.cpp b/app/render/playbackcache.cpp index d12325c75..2fd2e046f 100644 --- a/app/render/playbackcache.cpp +++ b/app/render/playbackcache.cpp @@ -27,7 +27,7 @@ namespace olive { -void PlaybackCache::Invalidate(const TimeRange &r, bool signal) +void PlaybackCache::Invalidate(const TimeRange &r) { if (r.in() == r.out()) { qWarning() << "Tried to invalidate zero-length range"; @@ -38,8 +38,10 @@ void PlaybackCache::Invalidate(const TimeRange &r, bool signal) InvalidateEvent(r); - if (signal) { - emit Invalidated(r); + emit Invalidated(r); + + if (automatic_) { + emit Request(r, false); } } @@ -73,11 +75,20 @@ Project *PlaybackCache::GetProject() const PlaybackCache::PlaybackCache(QObject *parent) : QObject(parent), - enabled_(false) + automatic_(false) { uuid_ = QUuid::createUuid(); } +void PlaybackCache::SetIsAutomatic(bool e) +{ + if (automatic_ != e) { + automatic_ = e; + + emit AutomaticChanged(automatic_); + } +} + TimeRangeList PlaybackCache::GetInvalidatedRanges(TimeRange intersecting) { TimeRangeList invalidated; diff --git a/app/render/playbackcache.h b/app/render/playbackcache.h index b10b48686..83c4365da 100644 --- a/app/render/playbackcache.h +++ b/app/render/playbackcache.h @@ -42,14 +42,8 @@ public: const QUuid &GetUuid() const { return uuid_; } void SetUuid(const QUuid &u) { uuid_ = u; } - bool IsEnabled() const { return enabled_; } - void SetEnabled(bool e) - { - if (enabled_ != e) { - enabled_ = e; - emit EnabledChanged(e); - } - } + bool IsAutomatic() const { return automatic_; } + void SetIsAutomatic(bool e); TimeRangeList GetInvalidatedRanges(TimeRange intersecting); TimeRangeList GetInvalidatedRanges(const rational &length) @@ -65,7 +59,7 @@ public: QString GetCacheDirectory() const; - void Invalidate(const TimeRange& r, bool signal = true); + void Invalidate(const TimeRange& r); const TimeRangeList &GetValidatedRanges() const { return validated_; } @@ -79,7 +73,9 @@ signals: void Validated(const olive::TimeRange& r); - void EnabledChanged(bool e); + void Request(const olive::TimeRange& r, bool previews_only); + + void AutomaticChanged(bool e); protected: void Validate(const TimeRange& r, bool signal = true); @@ -93,7 +89,7 @@ private: QUuid uuid_; - bool enabled_; + bool automatic_; }; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index f697a6aca..107c32acf 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -33,14 +33,11 @@ namespace olive { -// We may want to make this configurable at some point, so for now this constant is used as a -// placeholder for where that configarable variable would be used. -const bool PreviewAutoCacher::kRealTimeWaveformsEnabled = true; - -PreviewAutoCacher::PreviewAutoCacher() : +PreviewAutoCacher::PreviewAutoCacher(QObject *parent) : + QObject(parent), viewer_node_(nullptr), use_custom_range_(false), - pause_audio_(false), + pause_renders_(false), single_frame_render_(nullptr) { // Set defaults @@ -49,7 +46,7 @@ PreviewAutoCacher::PreviewAutoCacher() : // Wait a certain amount of time before requeuing when we receive an invalidate signal delayed_requeue_timer_.setInterval(OLIVE_CONFIG("AutoCacheDelay").toInt()); delayed_requeue_timer_.setSingleShot(true); - connect(&delayed_requeue_timer_, &QTimer::timeout, this, &PreviewAutoCacher::RequeueFrames); + connect(&delayed_requeue_timer_, &QTimer::timeout, this, &PreviewAutoCacher::TryRender); // Catch when a conform is ready connect(ConformManager::instance(), &ConformManager::ConformReady, this, &PreviewAutoCacher::ConformFinished); @@ -84,28 +81,18 @@ RenderTicketPtr PreviewAutoCacher::GetRangeOfAudio(TimeRange range, RenderTicket return RenderAudio(range, false, priority); } -void PreviewAutoCacher::VideoInvalidated(const TimeRange &range) +void PreviewAutoCacher::VideoInvalidatedFromCache(const TimeRange &range) { - // Stop any current render tasks because a) they might be out of date now anyway, and b) we - // want to dedicate all our rendering power to realtime feedback for the user - CancelVideoTasks(); + FrameHashCache *cache = static_cast(sender()); - // If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames - if (viewer_node_->video_frame_cache()->IsEnabled() && !NodeInputDragger::IsInputBeingDragged()) { - StartCachingVideoRange(range); - } + VideoInvalidatedFromNode(cache->parent(), range); } -void PreviewAutoCacher::AudioInvalidated(const TimeRange &range) +void PreviewAutoCacher::AudioInvalidatedFromCache(const TimeRange &range) { - // We don't stop rendering audio because currently there's no system of requeuing audio if it's - // cancelled, so some areas may end up unrendered forever - // ClearAudioQueue(); + AudioPlaybackCache *cache = static_cast(sender()); - // If we're auto-caching audio or require realtime waveforms, we'll have to render this - if (viewer_node_->audio_playback_cache()->IsEnabled() || kRealTimeWaveformsEnabled) { - StartCachingAudioRange(range); - } + AudioInvalidatedFromNode(cache->parent(), range); } void PreviewAutoCacher::AudioRendered() @@ -118,67 +105,38 @@ void PreviewAutoCacher::AudioRendered() if (audio_tasks_.contains(watcher)) { // Assume that a "result" is a fully completed image and a non-result is a cancelled ticket TimeRange range = audio_tasks_.take(watcher); + Node *node = Node::ValueToPtr(watcher->property("node")); if (watcher->HasResult()) { - // Remove this task from the list + AudioCacheData &d = audio_cache_data_[node]; + JobTime watcher_job_time = watcher->property("job").value(); - TimeRangeList valid_ranges = audio_job_tracker_.getCurrentSubRanges(range, watcher_job_time); + TimeRangeList valid_ranges = d.job_tracker.getCurrentSubRanges(range, watcher_job_time); AudioVisualWaveform waveform = watcher->GetTicket()->property("waveform").value(); - if (viewer_node_->audio_playback_cache()->IsEnabled()) { + SampleBuffer buf = watcher->Get().value(); + node->audio_playback_cache()->SetParameters(buf.audio_params()); + /*if (node->audio_playback_cache()->IsEnabled()) { // WritePCM is tolerant to its buffer being null, it will just write silence instead - viewer_node_->audio_playback_cache()->WritePCM(range, + node->audio_playback_cache()->WritePCM(range, valid_ranges, watcher->Get().value()); - } - - viewer_node_->audio_playback_cache()->WriteWaveform(range, valid_ranges, &waveform); + }*/ // Detect if this audio was incomplete because it was waiting on a conform to finish if (watcher->GetTicket()->property("incomplete").toBool()) { if (last_conform_task_ > watcher_job_time) { // Requeue now - viewer_node_->audio_playback_cache()->Invalidate(range); + node->audio_playback_cache()->Invalidate(range); } else { // Wait for conform - audio_needing_conform_.insert(range); - } - } else{ - // Retrieve visual waveforms - QVector waveform_list = watcher->GetTicket()->property("waveforms").value< QVector >(); - foreach (const RenderProcessor::RenderedWaveform& waveform_info, waveform_list) { - // Find original track - ClipBlock* block = nullptr; - - for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) { - if (it.value() == waveform_info.block) { - block = static_cast(it.key()); - break; - } - } - - if (block && !valid_ranges.isEmpty()) { - // Generate visual waveform in this background thread - block->waveform().set_channel_count(viewer_node_->GetAudioParams().channel_count()); - - // Determine which of the waveform ranges we got intersects with the valid ranges - TimeRangeList intersections = valid_ranges.Intersects(waveform_info.range + block->in()); - foreach (TimeRange r, intersections) { - // For each range, adjust it relative to the block and write it - r -= block->in(); - - if (waveform_info.silence) { - block->waveform().OverwriteSilence(r.in(), r.length()); - } else { - block->waveform().OverwriteSums(waveform_info.waveform, r.in(), r.in() - waveform_info.range.in(), r.length()); - } - } - - emit block->PreviewChanged(); - } + d.needing_conform.insert(range); } + } else { + qDebug() << "Writing waveforms to" << range << valid_ranges; + node->audio_playback_cache()->WriteWaveform(range, valid_ranges, &waveform); } } @@ -352,53 +310,64 @@ void PreviewAutoCacher::InsertIntoCopyMap(Node *node, Node *copy) void PreviewAutoCacher::ConnectToNodeCache(Node *node) { - // TEMP: Retain existing behavior until more work is done - if (node == viewer_node_) { - connect(node->video_frame_cache(), - &PlaybackCache::EnabledChanged, - this, - &PreviewAutoCacher::VideoAutoCacheEnableChanged); + connect(node->video_frame_cache(), + &PlaybackCache::AutomaticChanged, + this, + &PreviewAutoCacher::VideoAutoCacheEnableChanged); - connect(node->audio_playback_cache(), - &PlaybackCache::EnabledChanged, - this, - &PreviewAutoCacher::AudioAutoCacheEnableChanged); + connect(node->audio_playback_cache(), + &PlaybackCache::AutomaticChanged, + this, + &PreviewAutoCacher::AudioAutoCacheEnableChanged); - connect(node->video_frame_cache(), - &PlaybackCache::Invalidated, - this, - &PreviewAutoCacher::VideoInvalidated); + connect(node->video_frame_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::VideoInvalidatedFromCache); - connect(node->audio_playback_cache(), - &PlaybackCache::Invalidated, - this, - &PreviewAutoCacher::AudioInvalidated); + connect(node->audio_playback_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::AudioInvalidatedFromCache); + + // Copy invalidated ranges and start rendering if necessary + if (node->video_frame_cache()->IsAutomatic()) { + VideoAutoCacheEnableChangedFromNode(node, true); + } + + if (node->audio_playback_cache()->IsAutomatic()) { + AudioAutoCacheEnableChangedFromNode(node, true); } } void PreviewAutoCacher::DisconnectFromNodeCache(Node *node) { - // TEMP: Retain existing behavior until more work is done - if (node == viewer_node_) { - disconnect(node->video_frame_cache(), - &PlaybackCache::EnabledChanged, - this, - &PreviewAutoCacher::VideoAutoCacheEnableChanged); + disconnect(node->video_frame_cache(), + &PlaybackCache::AutomaticChanged, + this, + &PreviewAutoCacher::VideoAutoCacheEnableChanged); - disconnect(node->audio_playback_cache(), - &PlaybackCache::EnabledChanged, - this, - &PreviewAutoCacher::AudioAutoCacheEnableChanged); + disconnect(node->audio_playback_cache(), + &PlaybackCache::AutomaticChanged, + this, + &PreviewAutoCacher::AudioAutoCacheEnableChanged); - disconnect(node->video_frame_cache(), - &PlaybackCache::Invalidated, - this, - &PreviewAutoCacher::VideoInvalidated); + disconnect(node->video_frame_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::VideoInvalidatedFromCache); - disconnect(node->audio_playback_cache(), - &PlaybackCache::Invalidated, - this, - &PreviewAutoCacher::AudioInvalidated); + disconnect(node->audio_playback_cache(), + &PlaybackCache::Request, + this, + &PreviewAutoCacher::AudioInvalidatedFromCache); + + if (node->video_frame_cache()->IsAutomatic()) { + VideoAutoCacheEnableChangedFromNode(node, false); + } + + if (node->audio_playback_cache()->IsAutomatic()) { + AudioAutoCacheEnableChangedFromNode(node, false); } } @@ -421,17 +390,17 @@ void PreviewAutoCacher::CancelQueuedSingleFrameRender() } } -void PreviewAutoCacher::VideoInvalidatedList(const TimeRangeList &list) +void PreviewAutoCacher::VideoInvalidatedList(Node *node, const TimeRangeList &list) { foreach (const TimeRange &range, list) { - VideoInvalidated(range); + VideoInvalidatedFromNode(node, range); } } -void PreviewAutoCacher::AudioInvalidatedList(const TimeRangeList &list) +void PreviewAutoCacher::AudioInvalidatedList(Node *node, const TimeRangeList &list) { foreach (const TimeRange &range, list) { - AudioInvalidated(range); + AudioInvalidatedFromNode(node, range); } } @@ -441,24 +410,68 @@ void PreviewAutoCacher::StartCachingRange(const TimeRange &range, TimeRangeList tracker->insert(range, graph_changed_time_); } -void PreviewAutoCacher::StartCachingVideoRange(const TimeRange &range) +void PreviewAutoCacher::StartCachingVideoRange(Node *node, const TimeRange &range) { - StartCachingRange(range, &invalidated_video_, &video_job_tracker_); - RequeueFrames(); + VideoCacheData &d = video_cache_data_[node]; + + StartCachingRange(range, &d.invalidated, &d.job_tracker); + TryRender(); } -void PreviewAutoCacher::StartCachingAudioRange(const TimeRange &range) +void PreviewAutoCacher::StartCachingAudioRange(Node *node, const TimeRange &range) { - StartCachingRange(range, &invalidated_audio_, &audio_job_tracker_); + AudioCacheData &d = audio_cache_data_[node]; + + StartCachingRange(range, &d.invalidated, &d.job_tracker); TryRender(); } +void PreviewAutoCacher::VideoInvalidatedFromNode(Node *node, const TimeRange &range) +{ + // Stop any current render tasks because a) they might be out of date now anyway, and b) we + // want to dedicate all our rendering power to realtime feedback for the user + CancelVideoTasks(node); + + // If auto-cache is enabled and a slider is not being dragged, queue up to hash these frames + if (!NodeInputDragger::IsInputBeingDragged()) { + StartCachingVideoRange(node, range); + } +} + +void PreviewAutoCacher::AudioInvalidatedFromNode(Node *node, const TimeRange &range) +{ + // We don't stop rendering audio because currently there's no system of requeuing audio if it's + // cancelled, so some areas may end up unrendered forever + // ClearAudioQueue(); + + // If we're auto-caching audio or require realtime waveforms, we'll have to render this + StartCachingAudioRange(node, range); +} + +void PreviewAutoCacher::VideoAutoCacheEnableChangedFromNode(Node *node, bool e) +{ + if (e) { + VideoInvalidatedList(node, node->video_frame_cache()->GetInvalidatedRanges(node->GetVideoCacheRange())); + } else { + CancelVideoTasks(node); + } +} + +void PreviewAutoCacher::AudioAutoCacheEnableChangedFromNode(Node *node, bool e) +{ + if (e) { + AudioInvalidatedList(node, node->audio_playback_cache()->GetInvalidatedRanges(node->GetAudioCacheRange())); + } else { + CancelAudioTasks(node); + } +} + void PreviewAutoCacher::SetPlayhead(const rational &playhead) { cache_range_ = TimeRange(playhead - OLIVE_CONFIG("DiskCacheBehind").value(), playhead + OLIVE_CONFIG("DiskCacheAhead").value()); - RequeueFrames(); + TryRender(); } template @@ -487,9 +500,15 @@ void PreviewAutoCacher::CancelAudioTasks(bool and_wait_for_them_to_finish) CancelTasks(audio_tasks_, and_wait_for_them_to_finish); } -void PreviewAutoCacher::SetAudioPaused(bool e) +bool PreviewAutoCacher::IsRenderingCustomRange() const { - pause_audio_ = e; + const VideoCacheData &d = video_cache_data_.value(viewer_node_); + return d.iterator.IsCustomRange() && d.iterator.HasNext(); +} + +void PreviewAutoCacher::SetRendersPaused(bool e) +{ + pause_renders_ = e; if (!e) { TryRender(); } @@ -533,6 +552,12 @@ void PreviewAutoCacher::ValueHintChanged(const NodeInput &input) void PreviewAutoCacher::TryRender() { + delayed_requeue_timer_.stop(); + + if (pause_renders_) { + return; + } + if (!graph_update_queue_.isEmpty()) { // Check if we have jobs running in other threads that shouldn't be interrupted right now // NOTE: We don't check for downloads because, while they run in another thread, they don't @@ -546,24 +571,6 @@ void PreviewAutoCacher::TryRender() ProcessUpdateQueue(); } - // Check for newly invalidated video and hash it - if (!invalidated_video_.isEmpty()) { - if (!copied_viewer_node_->GetConnectedTextureOutput()) { - queued_frame_iterator_.reset(); - } else if (queued_frame_iterator_.HasNext()) { - queued_frame_iterator_.insert(invalidated_video_); - } else { - queued_frame_iterator_ = TimeRangeListFrameIterator(invalidated_video_, viewer_node_->GetVideoParams().frame_rate_as_time_base()); - } - invalidated_video_.clear(); - } - - if (!invalidated_audio_.isEmpty()) { - // Add newly invalidated audio to iterator - audio_iterator_.insert(invalidated_audio_); - invalidated_audio_.clear(); - } - if (single_frame_render_) { // Check if already caching this RenderTicketWatcher *watcher = RenderFrame(single_frame_render_->property("time").value(), @@ -578,36 +585,62 @@ void PreviewAutoCacher::TryRender() const int max_tasks = RenderManager::GetNumberOfIdealConcurrentJobs(); // Handle video tasks - rational t; - while (video_tasks_.size() < max_tasks && queued_frame_iterator_.GetNext(&t)) { - RenderTicketWatcher* render_task = video_tasks_.key(t); + for (auto it=video_cache_data_.begin(); it!=video_cache_data_.end(); it++) { + VideoCacheData &d = it.value(); - // We want this hash, if we're not already rendering, start render now - if (!render_task) { - // Don't render any hash more than once - RenderFrame(t, RenderTicketPriority::kNormal, viewer_node_->video_frame_cache()); + // Check for newly invalidated video + if (!d.invalidated.isEmpty()) { + if (d.iterator.HasNext()) { + d.iterator.insert(d.invalidated); + } else { + d.iterator = TimeRangeListFrameIterator(d.invalidated, viewer_node_->GetVideoParams().frame_rate_as_time_base()); + } + d.invalidated.clear(); } - emit SignalCacheProxyTaskProgress(double(queued_frame_iterator_.frame_index()) / double(queued_frame_iterator_.size())); + // Queue next frames + rational t; + while (video_tasks_.size() < max_tasks && d.iterator.GetNext(&t)) { + RenderTicketWatcher* render_task = video_tasks_.key(t); - if (!queued_frame_iterator_.HasNext()) { - emit StopCacheProxyTasks(); + // We want this hash, if we're not already rendering, start render now + if (!render_task) { + // Don't render any hash more than once + RenderFrame(it.key(), t, RenderTicketPriority::kNormal, it.key()->video_frame_cache()); + } + + emit SignalCacheProxyTaskProgress(double(d.iterator.frame_index()) / double(d.iterator.size())); + + if (!d.iterator.HasNext()) { + emit StopCacheProxyTasks(); + } } } + // Handle audio tasks - while (!audio_iterator_.isEmpty() && audio_tasks_.size() < max_tasks && !pause_audio_) { - // Copy first range in list - TimeRange r = audio_iterator_.first(); + for (auto it=audio_cache_data_.begin(); it!=audio_cache_data_.end(); it++) { + AudioCacheData &d = it.value(); - // Limit to the minimum sample rate supported by AudioVisualWaveform - we use this value so that - // whatever chunk we render can be summed down to the smallest mipmap whole - r.set_out(qMin(r.out(), r.in() + AudioVisualWaveform::kMinimumSampleRate.flipped())); + if (!d.invalidated.isEmpty()) { + // Add newly invalidated audio to iterator + d.iterator.insert(d.invalidated); + d.invalidated.clear(); + } - // Start job - RenderAudio(r, true, RenderTicketPriority::kNormal); + while (!d.iterator.isEmpty() && audio_tasks_.size() < max_tasks) { + // Copy first range in list + TimeRange r = d.iterator.first(); - audio_iterator_.remove(r); + // Limit to the minimum sample rate supported by AudioVisualWaveform - we use this value so that + // whatever chunk we render can be summed down to the smallest mipmap whole + r.set_out(qMin(r.out(), r.in() + AudioVisualWaveform::kMinimumSampleRate.flipped())); + + // Start job + RenderAudio(it.key(), r, true, RenderTicketPriority::kNormal); + + d.iterator.remove(r); + } } } @@ -616,6 +649,9 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); watcher->setProperty("cache", Node::PtrToValue(cache)); + if (cache) { + cache->SetTimebase(viewer_node_->GetVideoParams().frame_rate_as_time_base()); + } connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::VideoRendered); video_tasks_.insert(watcher, time); watcher->SetTicket(RenderManager::instance()->RenderFrame(node, @@ -632,8 +668,11 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, bool generate_waveforms, RenderTicketPriority priority) { + qDebug() << "Rendering" << r << "for" << node; + RenderTicketWatcher* watcher = new RenderTicketWatcher(); watcher->setProperty("job", QVariant::fromValue(last_update_time_)); + watcher->setProperty("node", Node::PtrToValue(node)); connect(watcher, &RenderTicketWatcher::Finished, this, &PreviewAutoCacher::AudioRendered); audio_tasks_.insert(watcher, r); @@ -642,75 +681,45 @@ RenderTicketPtr PreviewAutoCacher::RenderAudio(Node *node, const TimeRange &r, b return ticket; } -void PreviewAutoCacher::RequeueFrames() -{ - delayed_requeue_timer_.stop(); - - if (viewer_node_ - && (viewer_node_->video_frame_cache()->IsEnabled() || use_custom_range_) - && viewer_node_->video_frame_cache()->HasInvalidatedRanges(viewer_node_->GetVideoLength()) - && !IsRenderingCustomRange()) { - TimeRange using_range = use_custom_range_ ? custom_autocache_range_ : cache_range_; - - TimeRangeList invalidated = viewer_node_->video_frame_cache()->GetInvalidatedRanges(using_range); - queued_frame_iterator_ = TimeRangeListFrameIterator(invalidated, viewer_node_->video_frame_cache()->GetTimebase()); - - queued_frame_iterator_.SetCustomRange(use_custom_range_); - - emit StopCacheProxyTasks(); - - if (use_custom_range_) { - CustomCacheTask *cct = new CustomCacheTask(viewer_node_->GetLabelOrName()); - connect(this, &PreviewAutoCacher::StopCacheProxyTasks, cct, &CustomCacheTask::Finish); - connect(this, &PreviewAutoCacher::SignalCacheProxyTaskProgress, cct, &CustomCacheTask::ProgressChanged); - connect(cct, &CustomCacheTask::Cancelled, this, &PreviewAutoCacher::CacheProxyTaskCancelled); - TaskManager::instance()->AddTask(cct); - } - - use_custom_range_ = false; - - TryRender(); - } -} - void PreviewAutoCacher::ConformFinished() { // Got an audio conform, requeue all the audio currently needing a conform last_conform_task_.Acquire(); - if (!audio_needing_conform_.isEmpty()) { - // This list should be empty if there was a viewer switch - foreach (const TimeRange &range, audio_needing_conform_) { - viewer_node_->audio_playback_cache()->Invalidate(range); + for (auto it=audio_cache_data_.begin(); it!=audio_cache_data_.end(); it++) { + AudioCacheData &d = it.value(); + + if (!d.needing_conform.isEmpty()) { + // This list should be empty if there was a viewer switch + foreach (const TimeRange &range, d.needing_conform) { + it.key()->audio_playback_cache()->Invalidate(range); + } + d.needing_conform.clear(); } - audio_needing_conform_.clear(); } } void PreviewAutoCacher::VideoAutoCacheEnableChanged(bool e) { - if (e) { - VideoInvalidatedList(viewer_node_->video_frame_cache()->GetInvalidatedRanges(viewer_node_->GetVideoLength())); - } else { - CancelVideoTasks(); - queued_frame_iterator_.reset(); - } + FrameHashCache *cache = static_cast(sender()); + + VideoAutoCacheEnableChangedFromNode(cache->parent(), e); } void PreviewAutoCacher::AudioAutoCacheEnableChanged(bool e) { - if (e) { - AudioInvalidatedList(viewer_node_->audio_playback_cache()->GetInvalidatedRanges(viewer_node_->GetAudioLength())); - } else { - CancelAudioTasks(); - audio_iterator_.clear(); - } + AudioPlaybackCache *cache = static_cast(sender()); + + AudioAutoCacheEnableChangedFromNode(cache->parent(), e); } void PreviewAutoCacher::CacheProxyTaskCancelled() { - queued_frame_iterator_.reset(); - RequeueFrames(); + for (auto it=video_cache_data_.begin(); it!=video_cache_data_.end(); it++) { + it->iterator.reset(); + } + + TryRender(); } void PreviewAutoCacher::ForceCacheRange(const TimeRange &range) @@ -719,7 +728,7 @@ void PreviewAutoCacher::ForceCacheRange(const TimeRange &range) custom_autocache_range_ = range; // Re-hash these frames and start rendering - StartCachingVideoRange(range); + StartCachingVideoRange(viewer_node_, range); } void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) @@ -747,23 +756,12 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) CancelAudioTasks(true); } - // Clear iterators - queued_frame_iterator_.reset(); - audio_iterator_.clear(); - - // Clear any invalidated ranges - invalidated_video_.clear(); - invalidated_audio_.clear(); - // Clear any single frame render that might be queued CancelQueuedSingleFrameRender(); // Not interested in video passthroughs anymore video_immediate_passthroughs_.clear(); - // Not interested in audio conforming anymore - audio_needing_conform_.clear(); - // Disconnect from all node cache's for (auto it=copy_map_.cbegin(); it!=copy_map_.cend(); it++) { DisconnectFromNodeCache(it.key()); @@ -775,8 +773,10 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) copy_map_.clear(); copied_viewer_node_ = nullptr; graph_update_queue_.clear(); - video_job_tracker_.clear(); - audio_job_tracker_.clear(); + + // Ensure all cache data is cleared + video_cache_data_.clear(); + audio_cache_data_.clear(); // Disconnect signals for future node additions/deletions NodeGraph* graph = viewer_node_->parent(); @@ -795,6 +795,8 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) // Copy graph NodeGraph* graph = viewer_node_->parent(); + SetRendersPaused(true); + // Add all nodes for (int i=0; inodes().at(i), copied_project_.nodes().at(i)); @@ -826,9 +828,7 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node) connect(graph, &NodeGraph::ValueChanged, this, &PreviewAutoCacher::ValueChanged, Qt::DirectConnection); connect(graph, &NodeGraph::InputValueHintChanged, this, &PreviewAutoCacher::ValueHintChanged, Qt::DirectConnection); - // Copy invalidated ranges and start rendering if necessary - VideoInvalidatedList(viewer_node_->video_frame_cache()->GetInvalidatedRanges(viewer_node_->GetVideoLength())); - AudioInvalidatedList(viewer_node_->audio_playback_cache()->GetInvalidatedRanges(viewer_node_->GetAudioLength())); + SetRendersPaused(false); } } diff --git a/app/render/previewautocacher.h b/app/render/previewautocacher.h index 4c20a72d6..0572bbd31 100644 --- a/app/render/previewautocacher.h +++ b/app/render/previewautocacher.h @@ -47,7 +47,7 @@ class PreviewAutoCacher : public QObject { Q_OBJECT public: - PreviewAutoCacher(); + PreviewAutoCacher(QObject *parent = nullptr); virtual ~PreviewAutoCacher() override; @@ -85,12 +85,9 @@ public: void CancelVideoTasks(bool and_wait_for_them_to_finish = false); void CancelAudioTasks(bool and_wait_for_them_to_finish = false); - bool IsRenderingCustomRange() const - { - return queued_frame_iterator_.IsCustomRange() && queued_frame_iterator_.HasNext(); - } + bool IsRenderingCustomRange() const; - void SetAudioPaused(bool e); + void SetRendersPaused(bool e); signals: void StopCacheProxyTasks(); @@ -137,12 +134,18 @@ private: void CancelQueuedSingleFrameRender(); - void VideoInvalidatedList(const TimeRangeList &list); - void AudioInvalidatedList(const TimeRangeList &list); + void VideoInvalidatedList(Node *node, const TimeRangeList &list); + void AudioInvalidatedList(Node *node, const TimeRangeList &list); void StartCachingRange(const TimeRange &range, TimeRangeList *range_list, RenderJobTracker *tracker); - void StartCachingVideoRange(const TimeRange &range); - void StartCachingAudioRange(const TimeRange &range); + void StartCachingVideoRange(Node *node, const TimeRange &range); + void StartCachingAudioRange(Node *node, const TimeRange &range); + + void VideoInvalidatedFromNode(Node *node, const olive::TimeRange &range); + void AudioInvalidatedFromNode(Node *node, const olive::TimeRange &range); + + void VideoAutoCacheEnableChangedFromNode(Node *node, bool e); + void AudioAutoCacheEnableChangedFromNode(Node *node, bool e); class QueuedJob { public: @@ -177,15 +180,9 @@ private: bool use_custom_range_; TimeRange custom_autocache_range_; - TimeRangeList invalidated_video_; - TimeRangeList invalidated_audio_; - - bool pause_audio_; + bool pause_renders_; RenderTicketPtr single_frame_render_; - - QMap audio_tasks_; - QMap video_tasks_; QMap > video_immediate_passthroughs_; JobTime graph_changed_time_; @@ -193,28 +190,37 @@ private: QTimer delayed_requeue_timer_; - TimeRangeList audio_needing_conform_; - JobTime last_conform_task_; - RenderJobTracker video_job_tracker_; - RenderJobTracker audio_job_tracker_; + QMap audio_tasks_; + QMap video_tasks_; - TimeRangeListFrameIterator queued_frame_iterator_; - TimeRangeList audio_iterator_; + struct VideoCacheData { + TimeRangeList invalidated; + RenderJobTracker job_tracker; + TimeRangeListFrameIterator iterator; + }; - static const bool kRealTimeWaveformsEnabled; + struct AudioCacheData { + TimeRangeList invalidated; + TimeRangeList needing_conform; + RenderJobTracker job_tracker; + TimeRangeList iterator; + }; + + QHash video_cache_data_; + QHash audio_cache_data_; private slots: /** * @brief Handler for when the NodeGraph reports a video change over a certain time range */ - void VideoInvalidated(const olive::TimeRange &range); + void VideoInvalidatedFromCache(const olive::TimeRange &range); /** * @brief Handler for when the NodeGraph reports a audio change over a certain time range */ - void AudioInvalidated(const olive::TimeRange &range); + void AudioInvalidatedFromCache(const olive::TimeRange &range); /** * @brief Handler for when the RenderManager has returned rendered audio @@ -241,7 +247,7 @@ private slots: /** * @brief Generic function called whenever the frames to render need to be (re)queued */ - void RequeueFrames(); + //void RequeueFrames(); void ConformFinished(); diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 0b2d469b8..e1c72dd79 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -365,26 +365,6 @@ NodeValueTable RenderProcessor::GenerateBlockTable(const Track *track, const Tim NodeValueTable::Merge({merged_table, table}); } - - // Create block waveforms if requested - if (ticket_->property("enablewaveforms").toBool() && clip_cast) { - // Format information for use in the main thread - RenderedWaveform waveform_info; - waveform_info.block = clip_cast; - waveform_info.range = range_for_block - b->in(); - - if (!(waveform_info.silence = !samples_from_this_block.is_allocated())) { - // Generate a visual waveform from the samples acquired from this block - AudioVisualWaveform visual_waveform; - visual_waveform.set_channel_count(audio_params.channel_count()); - visual_waveform.OverwriteSamples(samples_from_this_block, audio_params.sample_rate()); - waveform_info.waveform = visual_waveform; - } - - QVector waveform_list = ticket_->property("waveforms").value< QVector >(); - waveform_list.append(waveform_info); - ticket_->setProperty("waveforms", QVariant::fromValue(waveform_list)); - } } } diff --git a/app/widget/timelinewidget/tool/pointer.cpp b/app/widget/timelinewidget/tool/pointer.cpp index 693ac109c..d0e066493 100644 --- a/app/widget/timelinewidget/tool/pointer.cpp +++ b/app/widget/timelinewidget/tool/pointer.cpp @@ -406,7 +406,7 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, bool multitrim_enabled = IsClipTrimmable(clicked_item, clips, trim_mode); // Create ghosts for trimming - foreach (Block* clip_item, clips) { + for (Block* clip_item : clips) { if (clip_item != clicked_item && (!multitrim_enabled || !IsClipTrimmable(clip_item, clips, trim_mode))) { // Either multitrim is disabled or this clip is NOT the earliest/latest in its track. We @@ -481,7 +481,7 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, // I'm only including it to prevent any potentially unintended behavior. if (clips.size() == 1 && !(modifiers & Qt::AltModifier)) { if (ClipBlock *adjacent_clip = dynamic_cast(adjacent)) { - foreach (Block *adjacent_link, adjacent_clip->block_links()) { + for (Block *adjacent_link : adjacent_clip->block_links()) { adjacent_ghosts.append(AddGhostFromBlock(adjacent_link, flipped_mode)); } } @@ -496,7 +496,7 @@ void PointerTool::InitiateDragInternal(Block *clicked_item, // expected to fill the remaining space (no gap needs to be created) ghost->SetData(TimelineViewGhostItem::kTrimIsARollEdit, static_cast(adjacent)); - foreach (TimelineViewGhostItem *adjacent_ghost, adjacent_ghosts) { + for (TimelineViewGhostItem *adjacent_ghost : adjacent_ghosts) { if (adjacent_ghost) { if (treat_trim_as_slide) { // We're sliding a transition rather than a pure trim/roll @@ -699,7 +699,7 @@ void PointerTool::FinishDrag(TimelineViewMouseEvent *event) // Place the copy instead of the original block block = static_cast(Node::CopyNodeInGraph(block, command)); if (ClipBlock *new_clip = dynamic_cast(block)) { - new_clip->waveform() = static_cast(p.block)->waveform(); + new_clip->set_waveform(static_cast(p.block)->waveform()); } } diff --git a/app/widget/timelinewidget/undo/timelineundosplit.cpp b/app/widget/timelinewidget/undo/timelineundosplit.cpp index 9d1ea40ce..74626750c 100644 --- a/app/widget/timelinewidget/undo/timelineundosplit.cpp +++ b/app/widget/timelinewidget/undo/timelineundosplit.cpp @@ -44,7 +44,7 @@ void BlockSplitCommand::redo() if (ClipBlock *new_clip = dynamic_cast(new_block_)) { ClipBlock *old_clip = static_cast(block_); - new_clip->waveform() = old_clip->waveform(); + new_clip->set_waveform(old_clip->waveform()); } // Determine our new lengths diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index 22c5daa4e..f7fd1a637 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -47,6 +47,7 @@ TimelineView::TimelineView(Qt::Alignment vertical_alignment, QWidget *parent) : ghosts_(nullptr), show_beam_cursor_(false), connected_track_list_(nullptr), + show_thumbnails_(true), show_waveforms_(true), transition_overlay_out_(nullptr), transition_overlay_in_(nullptr) @@ -519,12 +520,32 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q painter->drawRect(r); if (ClipBlock *clip = dynamic_cast(block)) { + QRect preview_rect = r.adjusted(0, text_total_height, 0, 0).toRect(); + + // Draw clip thumbnails + if (clip->GetTrackType() == Track::kVideo && show_thumbnails_ && preview_rect.height() > r.height()/3) { + const int kTempThumbWidth = 120; + const int kTempThumbHeight = 68; + + QRect thumb_rect; + painter->setClipRect(preview_rect); + for (int i=preview_rect.left(); ifillRect(thumb_rect, Qt::red); + } + painter->setClipping(false); + } + // Draw waveform - if (show_waveforms_) { - QRect waveform_rect = r.adjusted(0, text_total_height, 0, 0).toRect(); - painter->setPen(shadow_color); - AudioVisualWaveform::DrawWaveform(painter, waveform_rect, this->GetScale(), clip->waveform(), - SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()) + media_in); + if (clip->GetTrackType() == Track::kAudio && show_waveforms_) { + if (const AudioVisualWaveform *wave = clip->waveform()) { + rational waveform_start = SceneToTime(block_left - block_in, GetScale(), connected_track_list_->parent()->GetAudioParams().sample_rate_as_time_base()) + media_in; + painter->setPen(shadow_color); + + AudioVisualWaveform::DrawWaveform(painter, preview_rect, this->GetScale(), *wave, waveform_start); + } } // Draw zebra stripes and markers diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 3dc5989b3..28a8cb799 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -153,6 +153,7 @@ private: TrackList* connected_track_list_; + bool show_thumbnails_; bool show_waveforms_; ClipBlock *transition_overlay_out_; diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp index a32c5df02..5d6980bcb 100644 --- a/app/widget/viewer/viewer.cpp +++ b/app/widget/viewer/viewer.cpp @@ -147,6 +147,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) : setAcceptDrops(true); + auto_cacher_ = new PreviewAutoCacher(this); + connect(Core::instance(), &Core::ColorPickerEnabled, this, &ViewerWidget::SetSignalCursorColorEnabled); connect(this, &ViewerWidget::CursorColor, Core::instance(), &Core::ColorPickerColorEmitted); connect(AudioManager::instance(), &AudioManager::OutputParamsChanged, this, &ViewerWidget::UpdateAudioProcessor); @@ -191,7 +193,7 @@ void ViewerWidget::TimeChangedEvent(const rational &time) } // Send time to auto-cacher - auto_cacher_.SetPlayhead(time); + auto_cacher_->SetPlayhead(time); last_time_ = time; } @@ -275,7 +277,7 @@ void ViewerWidget::DisconnectNodeEvent(ViewerOutput *n) void ViewerWidget::ConnectedNodeChangeEvent(ViewerOutput *n) { - auto_cacher_.SetViewerNode(n); + auto_cacher_->SetViewerNode(n); display_widget_->SetSubtitleTracks(dynamic_cast(n)); } @@ -369,13 +371,13 @@ void ViewerWidget::SetFullScreen(QScreen *screen) void ViewerWidget::CacheEntireSequence() { - auto_cacher_.ForceCacheRange(TimeRange(0, GetConnectedNode()->GetVideoLength())); + auto_cacher_->ForceCacheRange(TimeRange(0, GetConnectedNode()->GetVideoLength())); } void ViewerWidget::CacheSequenceInOut() { if (GetConnectedNode() && GetConnectedNode()->GetTimelinePoints()->workarea()->enabled()) { - auto_cacher_.ForceCacheRange(GetConnectedNode()->GetTimelinePoints()->workarea()->range()); + auto_cacher_->ForceCacheRange(GetConnectedNode()->GetTimelinePoints()->workarea()->range()); } else { QMessageBox::warning(this, tr("Error"), @@ -442,12 +444,12 @@ void ViewerWidget::SetEmptyImage() void ViewerWidget::UpdateAutoCacher() { - auto_cacher_.SetPlayhead(GetTime()); + auto_cacher_->SetPlayhead(GetTime()); } void ViewerWidget::ClearVideoAutoCacherQueue() { - auto_cacher_.CancelVideoTasks(); + auto_cacher_->CancelVideoTasks(); } void ViewerWidget::DecrementPrequeuedAudio() @@ -549,7 +551,7 @@ void ViewerWidget::QueueNextAudioBuffer() RenderTicketWatcher *watcher = new RenderTicketWatcher(this); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::ReceivedAudioBufferForPlayback); audio_playback_queue_.push_back(watcher); - watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(audio_playback_queue_time_, queue_end), RenderTicketPriority::kHigh)); + watcher->SetTicket(auto_cacher_->GetRangeOfAudio(TimeRange(audio_playback_queue_time_, queue_end), RenderTicketPriority::kHigh)); audio_playback_queue_time_ = queue_end; } @@ -685,7 +687,7 @@ void ViewerWidget::UpdateTextureFromNode() nonqueue_watchers_.append(watcher); // Clear queue because we want this frame more than any others - if (!GetConnectedNode()->video_frame_cache()->IsEnabled() && !auto_cacher_.IsRenderingCustomRange()) { + if (!GetConnectedNode()->video_frame_cache()->IsAutomatic() && !auto_cacher_->IsRenderingCustomRange()) { ClearVideoAutoCacherQueue(); } @@ -716,10 +718,8 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only) foreach (ViewerWidget* viewer, instances_) { if (viewer != this) { viewer->PauseInternal(); - viewer->ClearVideoAutoCacherQueue(); + viewer->auto_cacher_->SetRendersPaused(true); } - - viewer->auto_cacher_.SetAudioPaused(true); } // Disarm recording if armed @@ -823,7 +823,7 @@ void ViewerWidget::PauseInternal() UpdateAudioProcessor(); foreach (ViewerWidget* viewer, instances_) { - viewer->auto_cacher_.SetAudioPaused(false); + viewer->auto_cacher_->SetRendersPaused(false); } UpdateTextureFromNode(); @@ -848,7 +848,7 @@ void ViewerWidget::PushScrubbedAudio() RenderTicketWatcher *watcher = new RenderTicketWatcher(); connect(watcher, &RenderTicketWatcher::Finished, this, &ViewerWidget::ReceivedAudioBufferForScrubbing); - watcher->SetTicket(auto_cacher_.GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval), RenderTicketPriority::kHigh)); + watcher->SetTicket(auto_cacher_->GetRangeOfAudio(TimeRange(GetTime(), GetTime() + interval), RenderTicketPriority::kHigh)); } } } @@ -922,7 +922,7 @@ RenderTicketPtr ViewerWidget::GetFrame(const rational &t, RenderTicketPriority p if (!QFileInfo::exists(cache_fn)) { // Frame hasn't been cached, start render job - return auto_cacher_.GetSingleFrame(t, priority); + return auto_cacher_->GetSingleFrame(t, priority); } else { // Frame has been cached, grab the frame RenderTicketPtr ticket = std::make_shared(); diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h index bc998748c..75f4a73e8 100644 --- a/app/widget/viewer/viewer.h +++ b/app/widget/viewer/viewer.h @@ -255,7 +255,7 @@ private: int prequeue_length_; int prequeue_count_; - PreviewAutoCacher auto_cacher_; + PreviewAutoCacher *auto_cacher_; QVector queue_watchers_;