made speed changes undoable
This commit is contained in:
+222
-27
@@ -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;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i);
|
||||
|
||||
// get frame rate
|
||||
if (frame_rate->isEnabled() && 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;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i);
|
||||
|
||||
// get percent
|
||||
long clip_default_length = qRound(c->getLength() * 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;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i);
|
||||
|
||||
// check if all selected clips are currently the same speed
|
||||
if (i == 0) {
|
||||
old_pc_val = c->speed;
|
||||
} 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;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i);
|
||||
|
||||
if (c->track >= 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;i<c->sequence->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;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i);
|
||||
close_clip(c);
|
||||
long proposed_out = c->timeline_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;i<c->sequence->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;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i);
|
||||
set_speed(ca, c, percent->value());
|
||||
}
|
||||
} 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;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i);
|
||||
if (i == 0) {
|
||||
cached_speed = c->speed;
|
||||
} 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;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i);
|
||||
if (c->track < 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;i<clips.size();i++) {
|
||||
Clip* c = clips.at(i);
|
||||
set_speed(ca, c, (c->getLength() * c->speed) / duration->value());
|
||||
}
|
||||
}
|
||||
|
||||
undo_stack.push(ca);
|
||||
|
||||
panel_timeline->redraw_all_clips(true);
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
+5
-19
@@ -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;
|
||||
|
||||
+16
-3
@@ -110,6 +110,9 @@ bool get_clip_frame(Clip* c, long playhead) {
|
||||
MediaStream* ms = static_cast<Media*>(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();
|
||||
}
|
||||
|
||||
+11
-4
@@ -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;i<effects.size();i++) {
|
||||
copy->effects.append(effects.at(i)->copy(copy));
|
||||
@@ -183,7 +186,8 @@ void Clip::recalculateMaxLength() {
|
||||
case MEDIA_TYPE_FOOTAGE:
|
||||
{
|
||||
Media* m = static_cast<Media*>(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<Sequence*>(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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user