improved speed/duration implementation

This functionality is almost complete minus the reverse speed and any of the
audio implementation.
This commit is contained in:
itsmattkc
2019-12-18 01:19:17 +11:00
parent f616a0d123
commit 29ae366659
4 changed files with 273 additions and 109 deletions
+238 -104
View File
@@ -3,7 +3,9 @@
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QVBoxLayout>
#include "common/timecodefunctions.h"
#include "undo/undostack.h"
@@ -17,145 +19,205 @@ SpeedDurationDialog::SpeedDurationDialog(const rational& timebase, const QList<C
{
setWindowTitle(tr("Speed/Duration"));
QGridLayout* layout = new QGridLayout(this);
QVBoxLayout* layout = new QVBoxLayout(this);
int row = 0;
{
// Create groupbox for the speed/duration
QGroupBox* speed_groupbox = new QGroupBox(tr("Speed/Duration"));
layout->addWidget(speed_groupbox);
QGridLayout* speed_layout = new QGridLayout(speed_groupbox);
original_speed_ = clips.first()->speed();
original_length_ = olive::time_to_timestamp(clips.first()->length(), timebase_);
int row = 0;
// Check all clips after the first to see if they all share the same speed or not
for (int i=1;i<clips.size();i++) {
// If all the clips aren't the same speed, we'll set to NaN
if (!qFuzzyCompare(original_speed_, clips.at(i)->speed())) {
original_speed_ = qSNaN();
// For any other clips that are selected, determine if they share speeds and lengths. If they don't, the UI can't
// show them all as having the same parameters
bool same_speed = true;
bool same_duration = true;
for (int i=1;i<clips.size();i++) {
ClipBlock* prev_clip = clips_.at(i-1);
ClipBlock* this_clip = clips_.at(i);
// Check if the speeds are different
if (same_speed
&& !qFuzzyCompare(prev_clip->speed(), this_clip->speed())) {
same_speed = false;
}
// Check if the durations are different
if (same_duration
&& prev_clip->length() != this_clip->length()) {
same_duration = false;
}
// If we've already determined both are different, no need to continue
if (!same_speed && !same_duration) {
break;
}
}
if (original_length_ != olive::time_to_timestamp(clips.at(i)->length(), timebase_)) {
original_length_ = -1;
speed_layout->addWidget(new QLabel(tr("Speed:")), row, 0);
// Create "Speed" slider
speed_slider_ = new FloatSlider();
speed_slider_->SetMinimum(0);
speed_layout->addWidget(speed_slider_, row, 1);
if (same_speed) {
// All clips share the same speed so we can show the value (converted to a percentage)
speed_slider_->SetValue(clips_.first()->speed() * 100.0);
} else {
// Else, we show an invalid initial state
speed_slider_->SetTristate();
}
if (original_length_ == -1 && qIsNaN(original_speed_)) {
break;
row++;
speed_layout->addWidget(new QLabel(tr("Duration:")), row, 0);
// Create "Duration" slider
duration_slider_ = new TimeSlider();
duration_slider_->SetTimebase(timebase_);
duration_slider_->SetMinimum(1);
speed_layout->addWidget(duration_slider_, row, 1);
if (same_duration) {
duration_slider_->SetValue(olive::time_to_timestamp(clips_.first()->length(), timebase_));
} else {
duration_slider_->SetTristate();
}
row++;
link_speed_and_duration_ = new QCheckBox(tr("Link Speed and Duration"));
link_speed_and_duration_->setChecked(true);
speed_layout->addWidget(link_speed_and_duration_, row, 0, 1, 2);
// Pick up when the speed or duration slider changes so we can programmatically link them
connect(speed_slider_, SIGNAL(ValueChanged(double)), this, SLOT(SpeedChanged()));
connect(duration_slider_, SIGNAL(ValueChanged(int64_t)), this, SLOT(DurationChanged()));
}
layout->addWidget(new QLabel(tr("Speed:")), row, 0);
speed_slider_ = new FloatSlider();
// Convert speed to percentage value
speed_slider_->SetValue(original_speed_ * 100.0);
connect(speed_slider_, SIGNAL(ValueChanged(double)), this, SLOT(SpeedChanged()));
layout->addWidget(speed_slider_, row, 1);
row++;
layout->addWidget(new QLabel(tr("Duration:")), row, 0);
duration_slider_ = new TimeSlider();
duration_slider_->SetTimebase(timebase_);
duration_slider_->SetValue(original_length_);
layout->addWidget(duration_slider_, row, 1);
if (!qIsNaN(original_speed_)) {
// Convert internally stored original length value to 1x speed for later calculations
original_length_ = qRound(static_cast<double>(original_length_) * original_speed_);
}
row++;
reverse_speed_checkbox_ = new QCheckBox(tr("Reverse Speed"));
layout->addWidget(reverse_speed_checkbox_, row, 0, 1, 2);
row++;
layout->addWidget(reverse_speed_checkbox_);
maintain_audio_pitch_checkbox_ = new QCheckBox(tr("Maintain Audio Pitch"));
layout->addWidget(maintain_audio_pitch_checkbox_, row, 0, 1, 2);
row++;
layout->addWidget(maintain_audio_pitch_checkbox_);
ripple_clips_checkbox_ = new QCheckBox(tr("Ripple Clips"));
layout->addWidget(ripple_clips_checkbox_, row, 0, 1, 2);
row++;
layout->addWidget(ripple_clips_checkbox_);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
buttons->setCenterButtons(true);
layout->addWidget(buttons, row, 0, 1, 2);
layout->addWidget(buttons);
connect(buttons, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttons, SIGNAL(rejected()), this, SLOT(reject()));
}
void SpeedDurationDialog::accept()
{
// FIXME: Support multiple incompatible speeds/lengths
if (duration_slider_->IsTristate() && speed_slider_->IsTristate()) {
// Nothing to be done
QDialog::accept();
return;
}
QUndoCommand* command = new QUndoCommand();
rational new_clip_length = olive::timestamp_to_time(duration_slider_->GetValue(), timebase_);
double new_speed = speed_slider_->GetValue() * 0.01;
foreach (ClipBlock* clip, clips_) {
rational this_clip_new_length = new_clip_length;
bool change_duration = !duration_slider_->IsTristate() || link_speed_and_duration_->isChecked();
bool change_speed = !speed_slider_->IsTristate() || link_speed_and_duration_->isChecked();
Block* next_block = clip->next();
double new_speed = speed_slider_->GetValue()*0.01;
if (this_clip_new_length != clip->length()
&& !ripple_clips_checkbox_->isChecked()
&& next_block) {
rational new_clip_length = clip->length();
if (this_clip_new_length > clip->length()) {
if (change_duration) {
// Change the duration
int64_t current_duration = olive::time_to_timestamp(clip->length(), timebase_);
int64_t new_duration = current_duration;
// Check if next clip is a gap, and if so we can take it all up
if (next_block->type() == Block::kGap) {
this_clip_new_length = qMin(next_block->out(), clip->in() + this_clip_new_length);
// Check if we're getting the duration value directly from the slider or calculating it from the speed
if (duration_slider_->IsTristate()) {
// Calculate duration from speed
new_duration = GetAdjustedDuration(clip, new_speed);
} else {
// Get duration directly from slider
new_duration = duration_slider_->GetValue();
// If we're taking up the entire clip, we'll just remove it
if (this_clip_new_length == next_block->out()) {
new TrackRippleRemoveBlockCommand(TrackOutput::TrackFromBlock(next_block), next_block, command);
// Check if we're calculating the speed from this duration
if (speed_slider_->IsTristate() && change_speed) {
// If we're here, the duration is going to override the speed
new_speed = GetAdjustedSpeed(clip, new_duration);
}
}
// Delete node and its exclusive deps
QList<Node*> gap_and_its_deps;
gap_and_its_deps.append(next_block);
gap_and_its_deps.append(next_block->GetExclusiveDependencies());
new NodeRemoveCommand(static_cast<NodeGraph*>(next_block->parent()), gap_and_its_deps, command);
} else {
// Otherwise we can just resize it
new BlockResizeCommand(next_block, next_block->out() - this_clip_new_length, command);
if (new_duration != current_duration) {
new_clip_length = olive::timestamp_to_time(new_duration, timebase_);
Block* next_block = clip->next();
// If "ripple clips" isn't checked, we need to calculate around the timeline as-is
if (!ripple_clips_checkbox_->isChecked()
&& next_block) {
if (new_clip_length > clip->length()) {
// Check if next clip is a gap, and if so we can take it all up
if (next_block->type() == Block::kGap) {
new_clip_length = qMin(next_block->out(), clip->in() + new_clip_length);
// If we're taking up the entire clip, we'll just remove it
if (new_clip_length == next_block->out()) {
new TrackRippleRemoveBlockCommand(TrackOutput::TrackFromBlock(next_block), next_block, command);
// Delete node and its exclusive deps
QList<Node*> gap_and_its_deps;
gap_and_its_deps.append(next_block);
gap_and_its_deps.append(next_block->GetExclusiveDependencies());
new NodeRemoveCommand(static_cast<NodeGraph*>(next_block->parent()), gap_and_its_deps, command);
} else {
// Otherwise we can just resize it
new BlockResizeCommand(next_block, next_block->out() - new_clip_length, command);
}
} else {
// Otherwise we can't extend any further
new_clip_length = clip->length();
}
} else if (new_clip_length < clip->length()) {
// If we're not rippling these clips, we'll need to insert a gap (unless the clip is already at the end)
rational gap_length = clip->length() - new_clip_length;
if (next_block->type() == Block::kGap) {
// If we've already got a gap here, we can just resize it
new BlockResizeCommand(next_block, next_block->length() + gap_length, command);
} else {
// Otherwise we have to create a new gap
GapBlock* gap = new GapBlock();
gap->set_length(gap_length);
new NodeAddCommand(static_cast<NodeGraph*>(clip->parent()), gap, command);
new TrackInsertBlockBetweenBlocksCommand(TrackOutput::TrackFromBlock(clip), gap, clip, next_block, command);
}
}
} else {
// Otherwise we can't extend any further
this_clip_new_length = clip->length();
}
} else if (this_clip_new_length < clip->length()) {
// If we're not rippling these clips, we'll need to insert a gap (unless the clip is already at the end)
rational gap_length = clip->length() - this_clip_new_length;
if (next_block->type() == Block::kGap) {
// If we've already got a gap here, we can just resize it
new BlockResizeCommand(next_block, next_block->length() + gap_length, command);
} else {
// Otherwise we have to create a new gap
GapBlock* gap = new GapBlock();
gap->set_length(gap_length);
new NodeAddCommand(static_cast<NodeGraph*>(clip->parent()), gap, command);
new TrackInsertBlockBetweenBlocksCommand(TrackOutput::TrackFromBlock(clip), gap, clip, next_block, command);
if (new_clip_length != clip->length()) {
new BlockResizeCommand(clip, new_clip_length, command);
}
}
}
if (this_clip_new_length != clip->length()) {
new BlockResizeWithoutMediaOutCommand(clip, this_clip_new_length, command);
}
if (change_speed) {
int64_t new_clip_duration = olive::time_to_timestamp(new_clip_length, timebase_);
int64_t new_media_duration = qRound(static_cast<double>(new_clip_duration) * new_speed);
rational new_media_length = olive::timestamp_to_time(new_media_duration, timebase_);
rational new_media_out = clip->media_in() + new_media_length;
int64_t media_length = qRound(static_cast<double>(olive::time_to_timestamp(this_clip_new_length, timebase_)) * new_speed);
new BlockSetMediaOutCommand(clip,
clip->media_in() + olive::timestamp_to_time(media_length, timebase_),
command);
// Change the speed by calculating the appropriate media out point for this clip
new BlockSetMediaOutCommand(clip, new_media_out, command);
}
}
olive::undo_stack.pushIfHasChildren(command);
@@ -163,19 +225,91 @@ void SpeedDurationDialog::accept()
QDialog::accept();
}
double SpeedDurationDialog::GetUnadjustedLengthTimestamp(ClipBlock *clip) const
{
double duration = static_cast<double>(olive::time_to_timestamp(clip->length(), timebase_));
// Convert duration to non-speed adjusted duration
duration *= clip->speed();
return duration;
}
int64_t SpeedDurationDialog::GetAdjustedDuration(ClipBlock *clip, const double &new_speed) const
{
double duration = GetUnadjustedLengthTimestamp(clip);
// Re-adjust by new speed
duration /= new_speed;
// Return rounded time
return qRound64(duration);
}
double SpeedDurationDialog::GetAdjustedSpeed(ClipBlock *clip, const int64_t &new_duration) const
{
double duration = GetUnadjustedLengthTimestamp(clip);
// Create a fraction of the original duration over the new duration
duration /= static_cast<double>(new_duration);
return duration;
}
void SpeedDurationDialog::SpeedChanged()
{
if (original_length_ > -1) {
if (link_speed_and_duration_->isChecked()) {
double new_speed = speed_slider_->GetValue()*0.01;
if (qIsNull(new_speed)) {
// 0 speed is considered a still image which could be any length and since we can't divide by zero anyway, we
// assume the length will stay the same
duration_slider_->SetValue(original_length_);
// A speed of 0 is considered a still frame. Since we can't divide by zero and a still frame could be any length,
// we don't bother updating the
return;
}
bool same_durations = true;
int64_t new_duration = GetAdjustedDuration(clips_.first(), new_speed);
for (int i=1;i<clips_.size();i++) {
// Calculate what this clip's duration will be
int64_t this_clip_duration = GetAdjustedDuration(clips_.at(i), new_speed);
// If they won't be the same, we won't show the value
if (this_clip_duration != new_duration) {
same_durations = false;
break;
}
}
if (same_durations) {
duration_slider_->SetValue(new_duration);
} else {
// Otherwise, calculate a length that will show the same amount of "content" with a new length
int64_t new_length = qRound(static_cast<double>(original_length_) / new_speed);
duration_slider_->SetValue(new_length);
duration_slider_->SetTristate();
}
}
}
void SpeedDurationDialog::DurationChanged()
{
if (link_speed_and_duration_->isChecked()) {
int64_t new_duration = duration_slider_->GetValue();
bool same_speeds = true;
double new_speed = GetAdjustedSpeed(clips_.first(), new_duration);
for (int i=1;i<clips_.size();i++) {
double this_clip_speed = GetAdjustedSpeed(clips_.at(i), new_duration);
if (!qFuzzyCompare(this_clip_speed, new_speed)) {
same_speeds = false;
break;
}
}
if (same_speeds) {
speed_slider_->SetValue(new_speed*100.0);
} else {
speed_slider_->SetTristate();
}
}
}
+9 -3
View File
@@ -19,6 +19,12 @@ public slots:
virtual void accept() override;
private:
double GetUnadjustedLengthTimestamp(ClipBlock* clip) const;
int64_t GetAdjustedDuration(ClipBlock* clip, const double& new_speed) const;
double GetAdjustedSpeed(ClipBlock* clip, const int64_t& new_duration) const;
QList<ClipBlock*> clips_;
FloatSlider* speed_slider_;
@@ -26,15 +32,15 @@ private:
rational timebase_;
double original_speed_;
int64_t original_length_;
QCheckBox* link_speed_and_duration_;
QCheckBox* reverse_speed_checkbox_;
QCheckBox* maintain_audio_pitch_checkbox_;
QCheckBox* ripple_clips_checkbox_;
private slots:
void SpeedChanged();
void DurationChanged();
};
#endif // SPEEDDURATIONDIALOG_H
+21 -2
View File
@@ -32,7 +32,8 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) :
has_max_(false),
mode_(mode),
dragged_(false),
require_valid_input_(true)
require_valid_input_(true),
tristate_(false)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
@@ -79,6 +80,17 @@ void SliderBase::SetAlignment(Qt::Alignment alignment)
label_->setAlignment(alignment);
}
bool SliderBase::IsTristate() const
{
return tristate_;
}
void SliderBase::SetTristate()
{
tristate_ = true;
UpdateLabel(0);
}
const QVariant &SliderBase::Value()
{
if (dragged_) {
@@ -92,6 +104,9 @@ void SliderBase::SetValue(const QVariant &v)
{
value_ = ClampValue(v);
// Disable tristate
tristate_ = false;
UpdateLabel(value_);
}
@@ -140,7 +155,11 @@ const QVariant &SliderBase::ClampValue(const QVariant &v)
void SliderBase::UpdateLabel(const QVariant &v)
{
label_->setText(ValueToString(v));
if (tristate_) {
label_->setText("---");
} else {
label_->setText(ValueToString(v));
}
}
QString SliderBase::ValueToString(const QVariant &v)
+5
View File
@@ -44,6 +44,9 @@ public:
void SetAlignment(Qt::Alignment alignment);
bool IsTristate() const;
void SetTristate();
signals:
void ValueChanged(QVariant v);
@@ -93,6 +96,8 @@ private:
bool require_valid_input_;
bool tristate_;
private slots:
void LabelPressed();