implemented speed/duration dialog and most of its functionality
An earlier commit implementing media in and out parameters was primarily for this addition. A simple control dialog for the clip's speed presentation reimplemented from the old codebase.
This commit is contained in:
@@ -9,6 +9,12 @@ rational::rational(const AVRational &r) :
|
||||
{
|
||||
}
|
||||
|
||||
rational rational::fromDouble(const double &flt)
|
||||
{
|
||||
// Use FFmpeg function for the time being
|
||||
return av_d2q(flt, INT_MAX);
|
||||
}
|
||||
|
||||
//Function: print number to cout
|
||||
|
||||
void rational::print(ostream &out) const
|
||||
|
||||
@@ -55,6 +55,8 @@ public:
|
||||
|
||||
rational(const AVRational& r);
|
||||
|
||||
static rational fromDouble(const double& flt);
|
||||
|
||||
//Assignment Operators
|
||||
const rational& operator=(const rational &rhs);
|
||||
const rational& operator+=(const rational &rhs);
|
||||
|
||||
@@ -21,6 +21,7 @@ add_subdirectory(footageproperties)
|
||||
add_subdirectory(preferences)
|
||||
add_subdirectory(projectproperties)
|
||||
add_subdirectory(sequence)
|
||||
add_subdirectory(speedduration)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
dialog/speedduration/speedduration.h
|
||||
dialog/speedduration/speedduration.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,181 @@
|
||||
#include "speedduration.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "undo/undostack.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
#include "widget/timelinewidget/undo/undo.h"
|
||||
|
||||
SpeedDurationDialog::SpeedDurationDialog(const rational& timebase, const QList<ClipBlock *> &clips, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
clips_(clips),
|
||||
timebase_(timebase)
|
||||
{
|
||||
setWindowTitle(tr("Speed/Duration"));
|
||||
|
||||
QGridLayout* layout = new QGridLayout(this);
|
||||
|
||||
int row = 0;
|
||||
|
||||
original_speed_ = clips.first()->speed();
|
||||
original_length_ = olive::time_to_timestamp(clips.first()->length(), timebase_);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
if (original_length_ != olive::time_to_timestamp(clips.at(i)->length(), timebase_)) {
|
||||
original_length_ = -1;
|
||||
}
|
||||
|
||||
if (original_length_ == -1 && qIsNaN(original_speed_)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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++;
|
||||
|
||||
maintain_audio_pitch_checkbox_ = new QCheckBox(tr("Maintain Audio Pitch"));
|
||||
layout->addWidget(maintain_audio_pitch_checkbox_, row, 0, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
ripple_clips_checkbox_ = new QCheckBox(tr("Ripple Clips"));
|
||||
layout->addWidget(ripple_clips_checkbox_, row, 0, 1, 2);
|
||||
|
||||
row++;
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
buttons->setCenterButtons(true);
|
||||
layout->addWidget(buttons, row, 0, 1, 2);
|
||||
connect(buttons, SIGNAL(accepted()), this, SLOT(accept()));
|
||||
connect(buttons, SIGNAL(rejected()), this, SLOT(reject()));
|
||||
}
|
||||
|
||||
void SpeedDurationDialog::accept()
|
||||
{
|
||||
// FIXME: Support multiple incompatible speeds/lengths
|
||||
|
||||
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;
|
||||
|
||||
Block* next_block = clip->next();
|
||||
|
||||
if (this_clip_new_length != clip->length()
|
||||
&& !ripple_clips_checkbox_->isChecked()
|
||||
&& next_block) {
|
||||
|
||||
if (this_clip_new_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) {
|
||||
this_clip_new_length = qMin(next_block->out(), clip->in() + this_clip_new_length);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
} 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 (this_clip_new_length != clip->length()) {
|
||||
new BlockResizeWithoutMediaOutCommand(clip, this_clip_new_length, command);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
olive::undo_stack.pushIfHasChildren(command);
|
||||
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
void SpeedDurationDialog::SpeedChanged()
|
||||
{
|
||||
if (original_length_ > -1) {
|
||||
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_);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifndef SPEEDDURATIONDIALOG_H
|
||||
#define SPEEDDURATIONDIALOG_H
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialog>
|
||||
#include <QUndoCommand>
|
||||
|
||||
#include "node/block/clip/clip.h"
|
||||
#include "widget/slider/floatslider.h"
|
||||
#include "widget/slider/timeslider.h"
|
||||
|
||||
class SpeedDurationDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SpeedDurationDialog(const rational& timebase, const QList<ClipBlock*>& clips, QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
virtual void accept() override;
|
||||
|
||||
private:
|
||||
QList<ClipBlock*> clips_;
|
||||
|
||||
FloatSlider* speed_slider_;
|
||||
TimeSlider* duration_slider_;
|
||||
|
||||
rational timebase_;
|
||||
|
||||
double original_speed_;
|
||||
int64_t original_length_;
|
||||
|
||||
QCheckBox* reverse_speed_checkbox_;
|
||||
QCheckBox* maintain_audio_pitch_checkbox_;
|
||||
QCheckBox* ripple_clips_checkbox_;
|
||||
|
||||
private slots:
|
||||
void SpeedChanged();
|
||||
};
|
||||
|
||||
#endif // SPEEDDURATIONDIALOG_H
|
||||
@@ -159,6 +159,11 @@ rational Block::media_length() const
|
||||
return media_out() - media_in();
|
||||
}
|
||||
|
||||
double Block::speed() const
|
||||
{
|
||||
return media_length().toDouble() / length().toDouble();
|
||||
}
|
||||
|
||||
const QString &Block::block_name() const
|
||||
{
|
||||
return block_name_;
|
||||
|
||||
@@ -72,6 +72,7 @@ public:
|
||||
void set_media_out(const rational& media_out);
|
||||
|
||||
rational media_length() const;
|
||||
double speed() const;
|
||||
|
||||
const QString& block_name() const;
|
||||
void set_block_name(const QString& name);
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
|
||||
#include "core.h"
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "dialog/speedduration/speedduration.h"
|
||||
#include "tool/tool.h"
|
||||
#include "trackview/trackview.h"
|
||||
#include "widget/menu/menu.h"
|
||||
#include "widget/nodeview/nodeviewundo.h"
|
||||
|
||||
TimelineWidget::TimelineWidget(QWidget *parent) :
|
||||
@@ -86,6 +88,7 @@ TimelineWidget::TimelineWidget(QWidget *parent) :
|
||||
connect(ruler_, SIGNAL(TimeChanged(const int64_t&)), view, SLOT(SetTime(const int64_t&)));
|
||||
connect(view, SIGNAL(TimeChanged(const int64_t&)), ruler_, SLOT(SetTime(const int64_t&)));
|
||||
connect(view, SIGNAL(TimeChanged(const int64_t&)), this, SIGNAL(TimeChanged(const int64_t&)));
|
||||
connect(view, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(ShowContextMenu()));
|
||||
connect(horizontal_scroll_, SIGNAL(valueChanged(int)), view->horizontalScrollBar(), SLOT(setValue(int)));
|
||||
connect(view->horizontalScrollBar(), SIGNAL(valueChanged(int)), horizontal_scroll_, SLOT(setValue(int)));
|
||||
|
||||
@@ -802,6 +805,32 @@ void TimelineWidget::TrackHeightChanged(TrackType type, int index, int height)
|
||||
}
|
||||
}
|
||||
|
||||
void TimelineWidget::ShowContextMenu()
|
||||
{
|
||||
Menu menu(this);
|
||||
|
||||
//QList<TimelineViewBlockItem*> selected = GetSelectedBlocks();
|
||||
QAction* speed_duration_action = menu.addAction(tr("Speed/Duration"));
|
||||
connect(speed_duration_action, SIGNAL(triggered(bool)), this, SLOT(ShowSpeedDurationDialog()));
|
||||
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void TimelineWidget::ShowSpeedDurationDialog()
|
||||
{
|
||||
QList<TimelineViewBlockItem*> selected = GetSelectedBlocks();
|
||||
QList<ClipBlock*> selected_clips;
|
||||
|
||||
foreach (TimelineViewBlockItem* item, selected) {
|
||||
if (item->block()->type() == Block::kClip) {
|
||||
selected_clips.append(static_cast<ClipBlock*>(item->block()));
|
||||
}
|
||||
}
|
||||
|
||||
SpeedDurationDialog speed_diag(timebase(), selected_clips, this);
|
||||
speed_diag.exec();
|
||||
}
|
||||
|
||||
void TimelineWidget::AddGhost(TimelineViewGhostItem *ghost)
|
||||
{
|
||||
ghost->SetScale(scale_);
|
||||
|
||||
@@ -415,6 +415,10 @@ private slots:
|
||||
|
||||
void TrackHeightChanged(TrackType type, int index, int height);
|
||||
|
||||
void ShowContextMenu();
|
||||
|
||||
void ShowSpeedDurationDialog();
|
||||
|
||||
};
|
||||
|
||||
#endif // TIMELINEWIDGET_H
|
||||
|
||||
@@ -182,6 +182,10 @@ void TimelineWidget::ImportTool::DragMove(TimelineViewMouseEvent *event)
|
||||
QString tooltip_text = olive::timestamp_to_timecode(earliest_timestamp,
|
||||
parent()->timebase(),
|
||||
olive::CurrentTimecodeDisplay());
|
||||
|
||||
// Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
|
||||
// of the cursor)
|
||||
QToolTip::hideText();
|
||||
QToolTip::showText(QCursor::pos(),
|
||||
tooltip_text,
|
||||
parent());
|
||||
|
||||
@@ -331,6 +331,10 @@ void TimelineWidget::PointerTool::ProcessDrag(const TimelineCoordinate &mouse_po
|
||||
parent()->timebase(),
|
||||
olive::CurrentTimecodeDisplay(),
|
||||
true);
|
||||
|
||||
// Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
|
||||
// of the cursor)
|
||||
QToolTip::hideText();
|
||||
QToolTip::showText(QCursor::pos(),
|
||||
tooltip_text,
|
||||
parent());
|
||||
|
||||
@@ -56,6 +56,9 @@ void TimelineWidget::SlipTool::ProcessDrag(const TimelineCoordinate &mouse_pos)
|
||||
parent()->timebase(),
|
||||
olive::CurrentTimecodeDisplay(),
|
||||
true);
|
||||
// Force tooltip to update (otherwise the tooltip won't move as written in the documentation, and could get in the way
|
||||
// of the cursor)
|
||||
QToolTip::hideText();
|
||||
QToolTip::showText(QCursor::pos(),
|
||||
tooltip_text,
|
||||
parent());
|
||||
|
||||
@@ -57,6 +57,24 @@ void BlockResizeCommand::undo()
|
||||
block_->set_length_and_media_out(old_length_);
|
||||
}
|
||||
|
||||
BlockResizeWithoutMediaOutCommand::BlockResizeWithoutMediaOutCommand(Block *block, rational new_length, QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
block_(block),
|
||||
old_length_(block->length()),
|
||||
new_length_(new_length)
|
||||
{
|
||||
}
|
||||
|
||||
void BlockResizeWithoutMediaOutCommand::redo()
|
||||
{
|
||||
block_->set_length(new_length_);
|
||||
}
|
||||
|
||||
void BlockResizeWithoutMediaOutCommand::undo()
|
||||
{
|
||||
block_->set_length(old_length_);
|
||||
}
|
||||
|
||||
BlockResizeWithMediaInCommand::BlockResizeWithMediaInCommand(Block *block, rational new_length, QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
block_(block),
|
||||
@@ -103,12 +121,12 @@ BlockSetMediaOutCommand::BlockSetMediaOutCommand(Block *block, rational new_medi
|
||||
|
||||
void BlockSetMediaOutCommand::redo()
|
||||
{
|
||||
block_->set_media_in(new_media_out_);
|
||||
block_->set_media_out(new_media_out_);
|
||||
}
|
||||
|
||||
void BlockSetMediaOutCommand::undo()
|
||||
{
|
||||
block_->set_media_in(old_media_out_);
|
||||
block_->set_media_out(old_media_out_);
|
||||
}
|
||||
|
||||
TrackRippleRemoveBlockCommand::TrackRippleRemoveBlockCommand(TrackOutput *track, Block *block, QUndoCommand *parent) :
|
||||
|
||||
@@ -41,6 +41,19 @@ private:
|
||||
rational new_length_;
|
||||
};
|
||||
|
||||
class BlockResizeWithoutMediaOutCommand : public QUndoCommand {
|
||||
public:
|
||||
BlockResizeWithoutMediaOutCommand(Block* block, rational new_length, QUndoCommand* parent = nullptr);
|
||||
|
||||
virtual void redo() override;
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
Block* block_;
|
||||
rational old_length_;
|
||||
rational new_length_;
|
||||
};
|
||||
|
||||
class BlockResizeWithMediaInCommand : public QUndoCommand {
|
||||
public:
|
||||
BlockResizeWithMediaInCommand(Block* block, rational new_length, QUndoCommand* parent = nullptr);
|
||||
|
||||
@@ -46,6 +46,7 @@ TimelineView::TimelineView(const TrackType &type, Qt::Alignment vertical_alignme
|
||||
setDragMode(NoDrag);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
setBackgroundRole(QPalette::Window);
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
|
||||
connect(&scene_, SIGNAL(changed(const QList<QRectF>&)), this, SLOT(UpdateSceneRect()));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user