From d0dc2661d98ed49b16b09ef52004c1b77c3c72a0 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Sat, 6 Oct 2018 17:16:30 +1000 Subject: [PATCH] made speed changes undoable --- dialogs/speeddialog.cpp | 249 +++++++++++++++++++++++++++++++++++----- playback/cacher.cpp | 24 +--- playback/playback.cpp | 19 ++- project/clip.cpp | 15 ++- project/clip.h | 4 +- project/undo.cpp | 30 +++++ project/undo.h | 22 ++++ 7 files changed, 308 insertions(+), 55 deletions(-) diff --git a/dialogs/speeddialog.cpp b/dialogs/speeddialog.cpp index 565e48fc1..be81177a2 100644 --- a/dialogs/speeddialog.cpp +++ b/dialogs/speeddialog.cpp @@ -15,6 +15,7 @@ #include "playback/playback.h" #include "panels/panels.h" #include "panels/timeline.h" +#include "project/undo.h" SpeedDialog::SpeedDialog(QWidget *parent) : QDialog(parent) { QVBoxLayout* main_layout = new QVBoxLayout(); @@ -63,6 +64,8 @@ SpeedDialog::SpeedDialog(QWidget *parent) : QDialog(parent) { void SpeedDialog::run() { bool enable_frame_rate = false; + bool multiple_audio = false; + maintain_pitch->setEnabled(false); default_frame_rate = qSNaN(); current_frame_rate = qSNaN(); @@ -108,6 +111,23 @@ void SpeedDialog::run() { enable_frame_rate = true; } + } else { + maintain_pitch->setEnabled(true); + + if (!multiple_audio) { + maintain_pitch->setChecked(c->maintain_audio_pitch); + multiple_audio = true; + } else if (!maintain_pitch->isTristate() && maintain_pitch->isChecked() != c->maintain_audio_pitch) { + maintain_pitch->setCheckState(Qt::PartiallyChecked); + maintain_pitch->setTristate(true); + } + } + + if (i == 0) { + reverse->setChecked(c->reverse); + } else if (c->reverse != reverse->isChecked()) { + reverse->setTristate(true); + reverse->setCheckState(Qt::PartiallyChecked); } // get default length @@ -127,64 +147,239 @@ void SpeedDialog::run() { current_percent = qSNaN(); } } + + } + frame_rate->set_minimum_value(1); + percent->set_minimum_value(0.0001); + duration->set_minimum_value(1); + frame_rate->setEnabled(enable_frame_rate); frame_rate->set_default_value(default_frame_rate); frame_rate->set_value(current_frame_rate, false); percent->set_value(current_percent, false); duration->set_default_value(default_length); - duration->set_value(current_length, false); + duration->set_value((current_length == -1) ? qSNaN() : current_length, false); exec(); } void SpeedDialog::percent_update() { - frame_rate->set_value(default_frame_rate * percent->value(), false); - duration->set_value(default_length / percent->value(), false); + bool got_fr = false; + double fr_val = qSNaN(); + long len_val = -1; + + for (int i=0;iisEnabled() && c->track < 0) { + double clip_fr = c->getMediaFrameRate() * percent->value(); + if (got_fr) { + if (!qIsNaN(fr_val) && !qFuzzyCompare(fr_val, clip_fr)) { + fr_val = qSNaN(); + } + } else { + fr_val = clip_fr; + got_fr = true; + } + } + + // get duration + long clip_default_length = qRound(c->getLength() * c->speed); + long new_clip_length = qRound(clip_default_length / percent->value()); + if (i == 0) { + len_val = new_clip_length; + } else if (len_val > -1 && len_val != new_clip_length) { + len_val = -1; + } + } + + frame_rate->set_value(fr_val, false); + duration->set_value((len_val == -1) ? qSNaN() : len_val, false); } void SpeedDialog::duration_update() { - double pc = default_length / duration->value(); - frame_rate->set_value(default_frame_rate * pc, false); - percent->set_value(pc, false); + double pc_val = qSNaN(); + bool got_fr = false; + double fr_val = qSNaN(); + + for (int i=0;igetLength() * c->speed); + double clip_pc = clip_default_length / duration->value(); + if (i == 0) { + pc_val = clip_pc; + } else if (!qIsNaN(pc_val) && !qFuzzyCompare(clip_pc, pc_val)) { + pc_val = qSNaN(); + } + + // get frame rate + if (frame_rate->isEnabled() && c->track < 0) { + double clip_fr = c->getMediaFrameRate() * clip_pc; + if (got_fr) { + if (!qIsNaN(fr_val) && !qFuzzyCompare(fr_val, clip_fr)) { + fr_val = qSNaN(); + } + } else { + fr_val = clip_fr; + got_fr = true; + } + } + } + + frame_rate->set_value(fr_val, false); + percent->set_value(pc_val, false); } void SpeedDialog::frame_rate_update() { - double fr = (frame_rate->value()); + /*double fr = (frame_rate->value()); double pc = (fr / default_frame_rate); percent->set_value(pc, false); - duration->set_value(default_length / pc, false); + duration->set_value(default_length / pc, false);*/ + + double old_pc_val = qSNaN(); + bool got_pc_val = false; + double pc_val = qSNaN(); + bool got_len_val = false; + long len_val = -1; + + // analyze video clips + for (int i=0;ispeed; + } else if (!qIsNaN(old_pc_val) && !qFuzzyCompare(c->speed, old_pc_val)) { + old_pc_val = qSNaN(); + } + + if (c->track < 0) { + // what would the new speed be based on this frame rate + double new_clip_speed = frame_rate->value() / c->getMediaFrameRate(); + if (!got_pc_val) { + pc_val = new_clip_speed; + got_pc_val = true; + } else if (!qIsNaN(pc_val) && !qFuzzyCompare(pc_val, new_clip_speed)) { + pc_val = qSNaN(); + } + + // what would be the new length based on this speed + long new_clip_len = (c->getLength() * c->speed) / new_clip_speed; + if (!got_len_val) { + len_val = new_clip_len; + got_len_val = true; + } else if (len_val > -1 && new_clip_len != len_val) { + len_val = -1; + } + } + } + + // analyze audio clips + for (int i=0;itrack >= 0) { + long new_clip_len = (qIsNaN(old_pc_val) || qIsNaN(pc_val)) ? c->getLength() : ((c->getLength() * c->speed) / pc_val); + if (len_val > -1 && new_clip_len != len_val) { + len_val = -1; + break; + } + } + } + + percent->set_value(pc_val, false); + duration->set_value((len_val == -1) ? qSNaN() : len_val, false); +} + +void set_speed(ComboAction* ca, Clip* c, double speed) { + long proposed_out = c->timeline_out; + double multiplier = (c->speed / speed); + proposed_out = c->timeline_in + (c->getLength() * multiplier); + ca->append(new SetSpeedAction(c, speed)); + if (proposed_out > c->timeline_out) { + for (int i=0;isequence->clips.size();i++) { + Clip* compare = c->sequence->clips.at(i); + if (compare != NULL + && compare->track == c->track + && compare->timeline_in >= c->timeline_out && compare->timeline_in < proposed_out) { + proposed_out = compare->timeline_in; + } + } + } + ca->append(new MoveClipAction(c, c->timeline_in, proposed_out, c->clip_in * multiplier, c->track)); } void SpeedDialog::accept() { - // TODO make undoable lmao + ComboAction* ca = new ComboAction(); + for (int i=0;itimeline_out; - if (!qIsNaN(percent->value())) { - double multiplier = (c->speed / percent->value()); - proposed_out = c->timeline_in + (c->getLength() * multiplier); - c->clip_in *= multiplier; - c->speed = percent->value(); + if (c->open) close_clip(c); + + if (c->track >= 0) { + if (maintain_pitch->checkState() != Qt::PartiallyChecked) { + ca->append(new SetBool(&c->maintain_audio_pitch, maintain_pitch->isChecked())); + } } - if (proposed_out > c->timeline_out) { - for (int i=0;isequence->clips.size();i++) { - Clip* compare = c->sequence->clips.at(i); - if (compare != NULL - && compare->track == c->track - && compare->timeline_in >= c->timeline_out && compare->timeline_in < proposed_out) { - proposed_out = compare->timeline_in; + + if (reverse->checkState() != Qt::PartiallyChecked) { + ca->append(new SetBool(&c->reverse, reverse->isChecked())); + } + } + + if (!qIsNaN(percent->value())) { + // simply set speed + for (int i=0;ivalue()); + } + } else if (!qIsNaN(frame_rate->value())) { + bool can_change_all = true; + double cached_speed; + double cached_fr = qSNaN(); + + // see if we can use the frame rate to change all the speeds + for (int i=0;ispeed; + } else if (!qFuzzyCompare(cached_speed, c->speed)) { + can_change_all = false; + } + if (c->track < 0) { + if (qIsNaN(cached_fr)) { + cached_fr = c->getMediaFrameRate(); + } else if (!qFuzzyCompare(cached_fr, c->getMediaFrameRate())) { + can_change_all = false; + break; } } } - c->timeline_out = proposed_out; - c->recalculateMaxLength(); - - c->maintain_audio_pitch = maintain_pitch->isChecked(); + // make changes + for (int i=0;itrack < 0) { + set_speed(ca, c, frame_rate->value() / c->getMediaFrameRate()); + } else if (can_change_all) { + set_speed(ca, c, frame_rate->value() / cached_fr); + } + } + } else if (!qIsNaN(duration->value())) { + // simply set duration + for (int i=0;igetLength() * c->speed) / duration->value()); + } } + + undo_stack.push(ca); + panel_timeline->redraw_all_clips(true); QDialog::accept(); } diff --git a/playback/cacher.cpp b/playback/cacher.cpp index 469637af0..414c3ce78 100644 --- a/playback/cacher.cpp +++ b/playback/cacher.cpp @@ -250,21 +250,11 @@ void cache_video_worker(Clip* c, long playhead, ClipCache* cache) { ret = retrieve_next_frame(c, c->frame); if (ret >= 0) { - // optimization: mathematically determine based on the sequence and clips' frame rates whether this frame will actually be shown - // note: is not actually much faster and currently leads to crashes, may remove in the future -// int proposed_frame = qFloor((i+cache->offset) * fr_ratio); - -// if (proposed_frame != c->last_cached_frame) { - if ((ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, c->frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { - qDebug() << "[ERROR] Could not feed filtergraph -" << ret; - error = true; - break; -// } else { -// c->last_cached_frame = proposed_frame; - } -// } else { -// i++; -// } + if ((ret = av_buffersrc_add_frame_flags(c->buffersrc_ctx, c->frame, AV_BUFFERSRC_FLAG_KEEP_REF)) < 0) { + qDebug() << "[ERROR] Could not feed filtergraph -" << ret; + error = true; + break; + } } else { if (ret == AVERROR_EOF) { c->reached_end = true; @@ -313,7 +303,6 @@ void reset_cache(Clip* c, long target_frame) { if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { // seeks to nearest keyframe (target_frame represents internal clip frame) - av_seek_frame(c->formatCtx, ms->file_index, (int64_t) qFloor(clip_frame_to_seconds(c, target_frame) / timebase), AVSEEK_FLAG_BACKWARD); // play up to the frame we actually want @@ -329,11 +318,8 @@ void reset_cache(Clip* c, long target_frame) { } while (retrieved_frame < target_frame); av_frame_free(&temp); - - c->last_cached_frame = -1; } else if (c->stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { // seek (target_frame represents timeline timecode in frames, not clip timecode) -// swr_drop_output(c->swr_ctx, swr_get_out_samples(c->swr_ctx, 0)); av_seek_frame(c->formatCtx, ms->file_index, playhead_to_seconds(c, target_frame) / timebase, AVSEEK_FLAG_BACKWARD); c->audio_target_frame = target_frame; c->frame_sample_index = -1; diff --git a/playback/playback.cpp b/playback/playback.cpp index d3a210d3b..8b4e188dd 100644 --- a/playback/playback.cpp +++ b/playback/playback.cpp @@ -110,6 +110,9 @@ bool get_clip_frame(Clip* c, long playhead) { MediaStream* ms = static_cast(c->media)->get_stream_from_file_index(c->track < 0, c->media_stream); long sequence_clip_time = playhead - c->timeline_in + c->clip_in; + + if (c->reverse && !ms->infinite_length) sequence_clip_time = c->getMaximumLength() - sequence_clip_time - 1; + long clip_time = refactor_frame_number(sequence_clip_time, c->sequence->frame_rate, c->getMediaFrameRate()*c->speed); AVFrame* current_frame = NULL; @@ -162,7 +165,8 @@ bool get_clip_frame(Clip* c, long playhead) { } } else { // this is technically bad, unless we just seeked - c->cache_A.unread = c->cache_B.unread = false; + c->cache_A.unread = false; + c->cache_B.unread = false; cache_needs_reset = true; } @@ -171,14 +175,23 @@ bool get_clip_frame(Clip* c, long playhead) { current_frame = cache[clip_time - cache_offset]; } - // determine whether we should s1tart filling the other cache + // determine whether we should start filling the other cache if (!using_cache_A || !using_cache_B) { if (c->lock.tryLock()) { + long cache_time; + if (cache_needs_reset) { + cache_time = (c->reverse) ? qMax(clip_time - c->cache_size, 0L) : qMax(clip_time, 0L); + } else if (c->reverse) { + cache_time = (cache_offset - c->cache_size); + } else { + cache_time = (cache_offset + c->cache_size); + } + bool write_A = (!using_cache_A && !c->cache_A.unread); bool write_B = (!using_cache_B && !c->cache_B.unread); if (write_A || write_B) { // if we have no cache and need to seek, start us at the current playhead, otherwise start at the end of the current cache - cache_clip(c, (cache_needs_reset) ? qMax(clip_time, 0L) : cache_offset + c->cache_size, write_A, write_B, cache_needs_reset, NULL); + cache_clip(c, cache_time, write_A, write_B, (cache_needs_reset || c->reverse), NULL); } c->lock.unlock(); } diff --git a/project/clip.cpp b/project/clip.cpp index 6f9894b94..3411eedbb 100644 --- a/project/clip.cpp +++ b/project/clip.cpp @@ -34,7 +34,8 @@ Clip::Clip(Sequence* s) : texture(NULL), fbo(NULL), autoscale(config.autoscale_by_default), - maintain_audio_pitch(false) + maintain_audio_pitch(false), + reverse(false) { reset(); } @@ -57,6 +58,8 @@ Clip* Clip::copy(Sequence* s) { copy->media_stream = media_stream; copy->autoscale = autoscale; copy->speed = speed; + copy->maintain_audio_pitch = maintain_audio_pitch; + copy->reverse = reverse; for (int i=0;ieffects.append(effects.at(i)->copy(copy)); @@ -183,7 +186,8 @@ void Clip::recalculateMaxLength() { case MEDIA_TYPE_FOOTAGE: { Media* m = static_cast(media); - if (m->get_stream_from_file_index(track < 0, media_stream)->infinite_length) { + MediaStream* ms = m->get_stream_from_file_index(track < 0, media_stream); + if (ms != NULL && ms->infinite_length) { calculated_length = LONG_MAX; } else { calculated_length = m->get_length_in_frames(fr); @@ -195,9 +199,12 @@ void Clip::recalculateMaxLength() { Sequence* s = static_cast(media); calculated_length = refactor_frame_number(s->getEndFrame(), s->frame_rate, fr); } - case MEDIA_TYPE_SOLID: - case MEDIA_TYPE_TONE: + break; + /*case MEDIA_TYPE_SOLID: + case MEDIA_TYPE_TONE:*/ + default: calculated_length = LONG_MAX; + break; } } diff --git a/project/clip.h b/project/clip.h index ddcbbb7d5..8df94121a 100644 --- a/project/clip.h +++ b/project/clip.h @@ -72,6 +72,7 @@ struct Clip int getWidth(); int getHeight(); double speed; + bool reverse; long calculated_length; // other variables (should be "duplicated" in copy()) @@ -102,8 +103,7 @@ struct Clip ClipCache cache_A; ClipCache cache_B; QMutex lock; - QMutex open_lock; - int last_cached_frame; + QMutex open_lock; // converters/filters AVFilterGraph* filter_graph; diff --git a/project/undo.cpp b/project/undo.cpp index 529648410..06857cbae 100644 --- a/project/undo.cpp +++ b/project/undo.cpp @@ -1458,3 +1458,33 @@ void DeleteMarkerAction::redo() { } sorted = true; } + +SetSpeedAction::SetSpeedAction(Clip* c, double speed) : + clip(c), + old_speed(c->speed), + new_speed(speed) +{} + +void SetSpeedAction::undo() { + clip->speed = old_speed; + clip->recalculateMaxLength(); +} + +void SetSpeedAction::redo() { + clip->speed = new_speed; + clip->recalculateMaxLength(); +} + +SetBool::SetBool(bool* b, bool setting) : + boolean(b), + old_setting(*b), + new_setting(setting) +{} + +void SetBool::undo() { + *boolean = old_setting; +} + +void SetBool::redo() { + *boolean = new_setting; +} diff --git a/project/undo.h b/project/undo.h index 60e38292f..77f1050b9 100644 --- a/project/undo.h +++ b/project/undo.h @@ -502,4 +502,26 @@ private: bool sorted; }; +class SetSpeedAction : public QUndoCommand { +public: + SetSpeedAction(Clip* c, double speed); + void undo(); + void redo(); +private: + Clip* clip; + double old_speed; + double new_speed; +}; + +class SetBool : public QUndoCommand { +public: + SetBool(bool* b, bool setting); + void undo(); + void redo(); +private: + bool* boolean; + bool old_setting; + bool new_setting; +}; + #endif // UNDO_H