diff --git a/app/common/timerange.cpp b/app/common/timerange.cpp index 7326794ef..b79555937 100644 --- a/app/common/timerange.cpp +++ b/app/common/timerange.cpp @@ -312,6 +312,11 @@ TimeRangeListFrameIterator::TimeRangeListFrameIterator(const TimeRangeList &list UpdateIndexIfNecessary(); } +rational TimeRangeListFrameIterator::Snap(const rational &r) const +{ + return Timecode::snap_time_to_timebase(r, timebase_, Timecode::kFloor); +} + bool TimeRangeListFrameIterator::GetNext(rational *out) { if (!HasNext()) { @@ -368,7 +373,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_ = Snap(list_.at(range_index_).in()); } } } diff --git a/app/common/timerange.h b/app/common/timerange.h index 882b14d36..b6235f9b8 100644 --- a/app/common/timerange.h +++ b/app/common/timerange.h @@ -207,6 +207,8 @@ public: TimeRangeListFrameIterator(); TimeRangeListFrameIterator(const TimeRangeList &list, const rational &timebase); + rational Snap(const rational &r) const; + bool GetNext(rational *out); bool HasNext() const; diff --git a/app/config/config.cpp b/app/config/config.cpp index 1e0ee70a2..ab2bbac5e 100644 --- a/app/config/config.cpp +++ b/app/config/config.cpp @@ -32,6 +32,7 @@ #include "common/filefunctions.h" #include "common/xmlutils.h" #include "core.h" +#include "timeline/timelinecommon.h" #include "ui/colorcoding.h" #include "ui/style/style.h" #include "window/mainwindow/mainwindow.h" @@ -104,6 +105,9 @@ void Config::SetDefaults() SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false); SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false); + SetEntryInternal(QStringLiteral("TimelineThumbnailMode"), NodeValue::kInt, Timeline::kThumbnailOn); + SetEntryInternal(QStringLiteral("TimelineWaveformMode"), NodeValue::kInt, Timeline::kWaveformsEnabled); + SetEntryInternal(QStringLiteral("DefaultVideoTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); SetEntryInternal(QStringLiteral("DefaultAudioTransition"), NodeValue::kText, QStringLiteral("org.olivevideoeditor.Olive.crossdissolve")); SetEntryInternal(QStringLiteral("DefaultTransitionLength"), NodeValue::kRational, QVariant::fromValue(rational(1))); diff --git a/app/node/block/clip/clip.cpp b/app/node/block/clip/clip.cpp index 201f37c43..08c43f86b 100644 --- a/app/node/block/clip/clip.cpp +++ b/app/node/block/clip/clip.cpp @@ -20,6 +20,7 @@ #include "clip.h" +#include "config/config.h" #include "node/output/track/track.h" #include "node/output/viewer/viewer.h" #include "widget/slider/floatslider.h" @@ -108,12 +109,14 @@ void ClipBlock::set_length_and_media_in(const rational &length) return; } - if (!reverse()) { - // Calculate media_in adjustment - set_media_in(SequenceToMediaTime(this->length() - length, kSTMIgnoreLoop)); - } + rational old_length = this->length(); super::set_length_and_media_in(length); + + if (!reverse()) { + // Calculate media_in adjustment + set_media_in(SequenceToMediaTime(old_length - length, kSTMIgnoreLoop)); + } } rational ClipBlock::media_in() const @@ -124,6 +127,8 @@ rational ClipBlock::media_in() const void ClipBlock::set_media_in(const rational &media_in) { SetStandardValue(kMediaInInput, QVariant::fromValue(media_in)); + + RequestInvalidatedFromConnected(); } void ClipBlock::SetAutocache(bool e) @@ -208,16 +213,22 @@ void ClipBlock::RequestRangeFromConnected(const TimeRange &range) if (type == Track::kVideo || type == Track::kAudio) { if (Node *connected = GetConnectedOutput(kBufferIn)) { - TimeRange max_range = InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); + TimeRange max_range = media_range(); if (type == Track::kVideo) { // Handle thumbnails - RequestRangeForCache(connected->thumbnail_cache(), max_range, range, true, true); + RequestRangeForCache(connected->thumbnail_cache(), max_range, range, true, false); + { + TimeRange thumb_range = range; + if (GetAdjustedThumbnailRange(&thumb_range)) { + emit connected->thumbnail_cache()->Request(thumb_range); + } + } // Handle video cache RequestRangeForCache(connected->video_frame_cache(), max_range, range, true, IsAutocaching()); } else if (type == Track::kAudio) { // Handle waveforms - RequestRangeForCache(connected->waveform_cache(), max_range, range, true, true); + RequestRangeForCache(connected->waveform_cache(), max_range, range, true, (OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled)); // Handle audio cache RequestRangeForCache(connected->audio_playback_cache(), max_range, range, true, IsAutocaching()); @@ -232,10 +243,13 @@ void ClipBlock::RequestInvalidatedFromConnected() if (type == Track::kVideo || type == Track::kAudio) { if (Node *connected = GetConnectedOutput(kBufferIn)) { - TimeRange max_range = InputTimeAdjustment(kBufferIn, -1, TimeRange(0, length())); + TimeRange max_range = media_range(); if (type == Track::kVideo) { // Handle thumbnails - RequestInvalidatedForCache(connected->thumbnail_cache(), max_range); + TimeRange thumb_range = max_range; + if (GetAdjustedThumbnailRange(&thumb_range)) { + RequestInvalidatedForCache(connected->thumbnail_cache(), thumb_range); + } // Handle video cache if (IsAutocaching()) { @@ -243,7 +257,9 @@ void ClipBlock::RequestInvalidatedFromConnected() } } else if (type == Track::kAudio) { // Handle waveforms - RequestInvalidatedForCache(connected->waveform_cache(), max_range); + if (OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled) { + RequestInvalidatedForCache(connected->waveform_cache(), max_range); + } // Handle audio cache if (IsAutocaching()) { @@ -280,6 +296,34 @@ void ClipBlock::RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange } } +bool ClipBlock::GetAdjustedThumbnailRange(TimeRange *r) const +{ + switch (static_cast(OLIVE_CONFIG("TimelineThumbnailMode").toInt())) { + case Timeline::kThumbnailOff: + // Don't cache any range + return false; + case Timeline::kThumbnailInOut: + { + // Only cache in point + rational in = this->media_range().in(); + if (r->Contains(in)) { + // Cache only the in point + *r = TimeRange(in, in + thumbnail_cache()->GetTimebase()); + return true; + } else { + // Cache nothing + return false; + } + } + case Timeline::kThumbnailOn: + // Cache entire range + return true; + } + + // Fallback + return true; +} + void ClipBlock::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) { Q_UNUSED(element) @@ -379,8 +423,10 @@ void ClipBlock::InputValueChangedEvent(const QString &input, int element) if (Node *connected = GetConnectedOutput(kBufferIn)) { if (type == Track::kVideo) { emit connected->video_frame_cache()->CancelAll(); + //emit connected->thumbnail_cache()->CancelAll(); } else if (type == Track::kAudio) { emit connected->audio_playback_cache()->CancelAll(); + //emit connected->waveform_cache()->CancelAll(); } } } diff --git a/app/node/block/clip/clip.h b/app/node/block/clip/clip.h index 9176341eb..8f98ee80f 100644 --- a/app/node/block/clip/clip.h +++ b/app/node/block/clip/clip.h @@ -232,6 +232,8 @@ private: void RequestRangeForCache(PlaybackCache *cache, const TimeRange &max_range, const TimeRange &range, bool invalidate, bool request); void RequestInvalidatedForCache(PlaybackCache *cache, const TimeRange &max_range); + bool GetAdjustedThumbnailRange(TimeRange *r) const; + QVector block_links_; TransitionBlock* in_transition_; diff --git a/app/render/framehashcache.h b/app/render/framehashcache.h index b98460c49..378e1a02a 100644 --- a/app/render/framehashcache.h +++ b/app/render/framehashcache.h @@ -93,6 +93,7 @@ public: ThumbnailCache(QObject* parent = nullptr) : FrameHashCache(parent) { + SetTimebase(rational(1, 10)); } }; diff --git a/app/render/previewautocacher.cpp b/app/render/previewautocacher.cpp index 587cd9553..beb94aeb2 100644 --- a/app/render/previewautocacher.cpp +++ b/app/render/previewautocacher.cpp @@ -459,8 +459,16 @@ void PreviewAutoCacher::StartCachingRange(const TimeRange &range, TimeRangeList void PreviewAutoCacher::StartCachingVideoRange(PlaybackCache *cache, const TimeRange &range) { Node *node = cache->parent(); - pending_video_jobs_.push_back({node, cache, range, TimeRangeListFrameIterator({range}, viewer_node_->GetVideoParams().frame_rate_as_time_base())}); - video_cache_data_[cache].job_tracker.insert(range, graph_changed_time_); + rational using_tb; + if (ThumbnailCache *thumbs = dynamic_cast(cache)) { + using_tb = thumbs->GetTimebase(); + } else { + using_tb = viewer_node_->GetVideoParams().frame_rate_as_time_base(); + } + + TimeRangeListFrameIterator iterator({range}, using_tb); + pending_video_jobs_.push_back({node, cache, range, iterator}); + video_cache_data_[cache].job_tracker.insert(TimeRange(iterator.Snap(range.in()), range.out()), graph_changed_time_); TryRender(); } @@ -679,8 +687,6 @@ RenderTicketWatcher* PreviewAutoCacher::RenderFrame(Node *node, const rational& rvp.video_params.set_divider(VideoParams::GetDividerForTargetResolution(rvp.video_params.width(), rvp.video_params.height(), 160, 120)); rvp.force_color_output = display_color_processor_; rvp.force_format = VideoParams::kFormatUnsigned8; - - wave_cache->SetTimebase(rational(1, 10)); } else { frame_cache->SetTimebase(viewer_node_->GetVideoParams().frame_rate_as_time_base()); } diff --git a/app/timeline/timelinecommon.h b/app/timeline/timelinecommon.h index 5c6d96df9..cdc513149 100644 --- a/app/timeline/timelinecommon.h +++ b/app/timeline/timelinecommon.h @@ -38,6 +38,17 @@ public: kTrimOut }; + enum ThumbnailMode { + kThumbnailOff, + kThumbnailInOut, + kThumbnailOn + }; + + enum WaveformMode { + kWaveformsDisabled, + kWaveformsEnabled + }; + static bool IsATrimMode(MovementMode mode) {return mode == kTrimIn || mode == kTrimOut;} struct EditToInfo { diff --git a/app/widget/timelinewidget/timelinewidget.cpp b/app/widget/timelinewidget/timelinewidget.cpp index 730560b9c..44e63c687 100644 --- a/app/widget/timelinewidget/timelinewidget.cpp +++ b/app/widget/timelinewidget/timelinewidget.cpp @@ -1140,14 +1140,20 @@ void TimelineWidget::ShowContextMenu() toggle_audio_units->setChecked(use_audio_time_units_); connect(toggle_audio_units, &QAction::triggered, this, &TimelineWidget::SetUseAudioTimeUnits); - QAction* show_thumbnails = menu.addAction(tr("Show Thumbnails")); - show_thumbnails->setCheckable(true); - show_thumbnails->setChecked(views_.first()->view()->GetShowThumbnails()); - connect(show_thumbnails, &QAction::triggered, this, &TimelineWidget::SetViewThumbnailsEnabled); + { + Menu *thumbnail_menu = new Menu(tr("Show Thumbnails"), &menu); + menu.addMenu(thumbnail_menu); + + thumbnail_menu->AddActionWithData(tr("Disabled"), Timeline::kThumbnailOff, OLIVE_CONFIG("TimelineThumbnailMode")); + thumbnail_menu->AddActionWithData(tr("Only At In/Out Points"), Timeline::kThumbnailInOut, OLIVE_CONFIG("TimelineThumbnailMode")); + thumbnail_menu->AddActionWithData(tr("Enabled"), Timeline::kThumbnailOn, OLIVE_CONFIG("TimelineThumbnailMode")); + + connect(thumbnail_menu, &Menu::triggered, this, &TimelineWidget::SetViewThumbnailsEnabled); + } QAction* show_waveforms = menu.addAction(tr("Show Waveforms")); show_waveforms->setCheckable(true); - show_waveforms->setChecked(views_.first()->view()->GetShowWaveforms()); + show_waveforms->setChecked(OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled); connect(show_waveforms, &QAction::triggered, this, &TimelineWidget::SetViewWaveformsEnabled); menu.addSeparator(); @@ -1211,16 +1217,14 @@ void TimelineWidget::AddableObjectChanged() void TimelineWidget::SetViewWaveformsEnabled(bool e) { - foreach (TimelineAndTrackView* tview, views_) { - tview->view()->SetShowWaveforms(e); - } + OLIVE_CONFIG("TimelineWaveformMode") = e ? Timeline::kWaveformsEnabled : Timeline::kWaveformsDisabled; + UpdateViewports(); } -void TimelineWidget::SetViewThumbnailsEnabled(bool e) +void TimelineWidget::SetViewThumbnailsEnabled(QAction *action) { - foreach (TimelineAndTrackView* tview, views_) { - tview->view()->SetShowThumbnails(e); - } + OLIVE_CONFIG("TimelineThumbnailMode") = action->data(); + UpdateViewports(); } void TimelineWidget::FrameRateChanged() diff --git a/app/widget/timelinewidget/timelinewidget.h b/app/widget/timelinewidget/timelinewidget.h index 138dc39d4..b2f869443 100644 --- a/app/widget/timelinewidget/timelinewidget.h +++ b/app/widget/timelinewidget/timelinewidget.h @@ -423,7 +423,7 @@ private slots: void SetViewWaveformsEnabled(bool e); - void SetViewThumbnailsEnabled(bool e); + void SetViewThumbnailsEnabled(QAction *action); void FrameRateChanged(); diff --git a/app/widget/timelinewidget/view/timelineview.cpp b/app/widget/timelinewidget/view/timelineview.cpp index bcb9fe2c2..555d3bc62 100644 --- a/app/widget/timelinewidget/view/timelineview.cpp +++ b/app/widget/timelinewidget/view/timelineview.cpp @@ -46,8 +46,6 @@ 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) { @@ -486,7 +484,6 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q int text_height = fm.height(); int text_padding = text_height/4; // This ties into the track minimum height being 1.5 int text_total_height = text_height + text_padding + text_padding; - Q_UNUSED(text_total_height) if (foreground) { painter->setBrush(Qt::NoBrush); @@ -524,43 +521,52 @@ void TimelineView::DrawBlock(QPainter *painter, bool foreground, Block *block, q QRect preview_rect = r.toRect(); // Draw clip thumbnails - if (clip->GetTrackType() == Track::kVideo && show_thumbnails_ && preview_rect.height() > r.height()/3) { + if (clip->GetTrackType() == Track::kVideo + && OLIVE_CONFIG("TimelineThumbnailMode").toInt() != Timeline::kThumbnailOff + && preview_rect.height() > r.height()/3) { if (const FrameHashCache *thumbs = clip->thumbnails()) { + // Start thumbnails underneath clip name + preview_rect.adjust(0, text_total_height, 0, 0); + QRect thumb_rect; - painter->setClipRect(preview_rect); painter->setRenderHint(QPainter::SmoothPixmapTransform); + painter->setClipRect(preview_rect); - Sequence *s = clip->track()->sequence(); - int width = s->GetVideoParams().width(); - int height = s->GetVideoParams().height(); - int start; - if (height > 0) { // Prevent divide by zero/invalid params - double scale = double(preview_rect.height())/double(height); - thumb_rect.setWidth(width * scale); - start = (preview_rect.left() - int(qFloor(block_in))) / thumb_rect.width() + qFloor(block_in); - } else { - start = preview_rect.left(); - } + if (OLIVE_CONFIG("TimelineThumbnailMode") == Timeline::kThumbnailOn) { - for (int i=start; iparent()->GetVideoParams().frame_rate_as_time_base()) + media_in; - QString thumbnail = thumbs->GetValidCacheFilename(time_here); - - if (!thumbnail.isEmpty()) { - QImage img; - if (img.load(thumbnail, "jpg")) { - double scale = double(preview_rect.height())/double(img.height()); - thumb_rect = QRect(i, preview_rect.top(), img.width() * scale, preview_rect.height()); - painter->drawImage(thumb_rect, img); - } + Sequence *s = clip->track()->sequence(); + int width = s->GetVideoParams().width(); + int height = s->GetVideoParams().height(); + int start; + if (height > 0) { // Prevent divide by zero/invalid params + double scale = double(preview_rect.height())/double(height); + thumb_rect.setWidth(width * scale); + start = (((preview_rect.left() - int(qFloor(block_in))) / thumb_rect.width()) * thumb_rect.width()) + qFloor(block_in); + } else { + start = preview_rect.left(); } + + for (int i=start; iparent()->GetVideoParams().frame_rate_as_time_base()) + media_in; + DrawThumbnail(painter, thumbs, time_here, i, preview_rect, &thumb_rect); + } + + } else { + + rational time = clip->media_range().in(); + time = Timecode::snap_time_to_timebase(time, thumbs->GetTimebase(), Timecode::kFloor); + DrawThumbnail(painter, thumbs, time, block_left, preview_rect, &thumb_rect); + } + painter->setClipping(false); + } } // Draw waveform - if (clip->GetTrackType() == Track::kAudio && show_waveforms_) { + if (clip->GetTrackType() == Track::kAudio + && OLIVE_CONFIG("TimelineWaveformMode").toInt() == Timeline::kWaveformsEnabled) { if (const AudioWaveformCache *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); @@ -733,6 +739,20 @@ qreal TimelineView::GetTimelineRightBound() const return GetTimelineLeftBound() + viewport()->width(); } +void TimelineView::DrawThumbnail(QPainter *painter, const FrameHashCache *thumbs, const rational &time, int x, const QRect &preview_rect, QRect *thumb_rect) const +{ + QString thumbnail = thumbs->GetValidCacheFilename(time); + + if (!thumbnail.isEmpty()) { + QImage img; + if (img.load(thumbnail, "jpg")) { + double scale = double(preview_rect.height())/double(img.height()); + *thumb_rect = QRect(x, preview_rect.top(), img.width() * scale, preview_rect.height()); + painter->drawImage(*thumb_rect, img); + } + } +} + int TimelineView::GetTrackY(int track_index) const { if (!connected_track_list_ || !connected_track_list_->GetTrackCount()) { diff --git a/app/widget/timelinewidget/view/timelineview.h b/app/widget/timelinewidget/view/timelineview.h index 9dea755f2..6ce72ca01 100644 --- a/app/widget/timelinewidget/view/timelineview.h +++ b/app/widget/timelinewidget/view/timelineview.h @@ -73,28 +73,6 @@ public: Block* GetItemAtScenePos(const rational& time, int track_index) const; - bool GetShowWaveforms() const - { - return show_waveforms_; - } - - void SetShowWaveforms(bool e) - { - show_waveforms_ = e; - viewport()->update(); - } - - bool GetShowThumbnails() const - { - return show_thumbnails_; - } - - void SetShowThumbnails(bool e) - { - show_thumbnails_ = e; - viewport()->update(); - } - signals: void MousePressed(TimelineViewMouseEvent* event); void MouseMoved(TimelineViewMouseEvent* event); @@ -154,6 +132,8 @@ private: qreal GetTimelineRightBound() const; + void DrawThumbnail(QPainter *painter, const FrameHashCache *thumbs, const rational &time, int x, const QRect &preview_rect, QRect *thumb_rect) const; + QHash* selections_; QVector* ghosts_; @@ -164,9 +144,6 @@ private: TrackList* connected_track_list_; - bool show_thumbnails_; - bool show_waveforms_; - ClipBlock *transition_overlay_out_; ClipBlock *transition_overlay_in_;