diff --git a/.travis/install.sh b/.travis/install.sh
index fa29d2ef8..8d57de697 100644
--- a/.travis/install.sh
+++ b/.travis/install.sh
@@ -6,7 +6,13 @@ if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
elif [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
- sudo apt-get -y -o Dpkg::Options::="--force-overwrite" install qt511base qt511multimedia qt511svg qt511tools libavformat-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev libswresample-dev cmake libopencolorio-dev libopenimageio-dev libgl1-mesa-dev
+ sudo apt-get -y -o Dpkg::Options::="--force-overwrite" install qt511base qt511multimedia qt511svg qt511tools libavformat-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev libswresample-dev libopencolorio-dev libopenimageio-dev libgl1-mesa-dev
source /opt/qt*/bin/qt*-env.sh
+ # Acquire latest cmake (apt somehow gets the wrong version?)
+ wget -c https://github.com/Kitware/CMake/releases/download/v3.17.2/cmake-3.17.2-Linux-x86_64.sh -O cmake.sh
+ chmod +x cmake.sh
+ ./cmake.sh --skip-license --prefix=cmake --exclude-dir
+ export PATH=$PWD/cmake/bin:$PATH
+
fi
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ccb6e9478..ee3cacf7e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
project(olive-editor VERSION 0.2.0 LANGUAGES CXX)
@@ -41,7 +41,7 @@ find_package(OpenGL REQUIRED)
find_package(OpenColorIO REQUIRED)
-find_package(OpenImageIO REQUIRED)
+find_package(OpenImageIO 1.6 REQUIRED)
find_package(OpenEXR REQUIRED)
diff --git a/app/codec/ffmpeg/ffmpegdecoder.cpp b/app/codec/ffmpeg/ffmpegdecoder.cpp
index abea45088..5b2c45a90 100644
--- a/app/codec/ffmpeg/ffmpegdecoder.cpp
+++ b/app/codec/ffmpeg/ffmpegdecoder.cpp
@@ -835,7 +835,7 @@ bool FFmpegDecoder::ConformAudio(const QAtomicInt *cancelled, const AudioRenderi
p.channel_layout(),
FFmpegCommon::GetFFmpegSampleFormat(p.format()),
p.sample_rate(),
- static_cast(index_instance.stream()->codecpar->channel_layout),
+ channel_layout,
static_cast(index_instance.stream()->codecpar->format),
index_instance.stream()->codecpar->sample_rate,
0,
diff --git a/app/dialog/export/codec/h264section.cpp b/app/dialog/export/codec/h264section.cpp
index 42833f665..e0e50fb55 100644
--- a/app/dialog/export/codec/h264section.cpp
+++ b/app/dialog/export/codec/h264section.cpp
@@ -121,6 +121,7 @@ H264CRFSection::H264CRFSection(QWidget *parent) :
crf_input->SetMinimum(kMinimumCRF);
crf_input->SetMaximum(kMaximumCRF);
crf_input->SetValue(kDefaultCRF);
+ crf_input->SetDefaultValue(kDefaultCRF);
layout->addWidget(crf_input);
connect(crf_slider_, &QSlider::valueChanged, crf_input, &IntegerSlider::SetValue);
diff --git a/app/dialog/export/export.cpp b/app/dialog/export/export.cpp
index 23480684b..3071d92c5 100644
--- a/app/dialog/export/export.cpp
+++ b/app/dialog/export/export.cpp
@@ -204,7 +204,9 @@ ExportDialog::ExportDialog(ViewerOutput *viewer_node, QWidget *parent) :
// Set defaults
format_combobox_->setCurrentIndex(kFormatMPEG4);
video_tab_->width_slider()->SetValue(viewer_node_->video_params().width());
+ video_tab_->width_slider()->SetDefaultValue(viewer_node_->video_params().width());
video_tab_->height_slider()->SetValue(viewer_node_->video_params().height());
+ video_tab_->height_slider()->SetDefaultValue(viewer_node_->video_params().height());
video_tab_->set_frame_rate(viewer_node_->video_params().time_base().flipped());
audio_tab_->set_sample_rate(viewer_node_->audio_params().sample_rate());
audio_tab_->set_channel_layout(viewer_node_->audio_params().channel_layout());
diff --git a/app/dialog/export/exportadvancedvideodialog.cpp b/app/dialog/export/exportadvancedvideodialog.cpp
index bbaba2ee6..1b11d85b9 100644
--- a/app/dialog/export/exportadvancedvideodialog.cpp
+++ b/app/dialog/export/exportadvancedvideodialog.cpp
@@ -19,6 +19,7 @@ ExportAdvancedVideoDialog::ExportAdvancedVideoDialog(QWidget *parent) :
thread_slider_ = new IntegerSlider();
thread_slider_->SetMinimum(0);
+ thread_slider_->SetDefaultValue(0);
layout->addWidget(thread_slider_, row, 1);
row++;
diff --git a/app/dialog/speedduration/speedduration.cpp b/app/dialog/speedduration/speedduration.cpp
index 4c6fc4ac9..84e1b1875 100644
--- a/app/dialog/speedduration/speedduration.cpp
+++ b/app/dialog/speedduration/speedduration.cpp
@@ -93,6 +93,7 @@ SpeedDurationDialog::SpeedDurationDialog(const rational& timebase, const QListSetMinimum(0);
speed_slider_->SetDisplayType(FloatSlider::kPercentage);
+ speed_slider_->SetDefaultValue(1);
speed_layout->addWidget(speed_slider_, row, 1);
if (same_speed) {
@@ -113,6 +114,9 @@ SpeedDurationDialog::SpeedDurationDialog(const rational& timebase, const QListSetMinimum(1);
speed_layout->addWidget(duration_slider_, row, 1);
+ // Calculate duration that would occur if the speed was 100%
+ duration_slider_->SetDefaultValue(GetUnadjustedLengthTimestamp(clips_.first()));
+
if (same_duration) {
duration_slider_->SetValue(Timecode::time_to_timestamp(clips_.first()->length(), timebase_));
} else {
diff --git a/app/node/audio/pan/pan.cpp b/app/node/audio/pan/pan.cpp
index c9ef24520..156c69818 100644
--- a/app/node/audio/pan/pan.cpp
+++ b/app/node/audio/pan/pan.cpp
@@ -27,7 +27,7 @@ PanNode::PanNode()
samples_input_ = new NodeInput("samples_in", NodeParam::kSamples);
AddInput(samples_input_);
- panning_input_ = new NodeInput("panning_in", NodeParam::kFloat);
+ panning_input_ = new NodeInput("panning_in", NodeParam::kFloat, 0.0);
panning_input_->set_property("min", -1.0);
panning_input_->set_property("max", 1.0);
panning_input_->set_property("view", "percent");
diff --git a/app/node/audio/volume/volume.cpp b/app/node/audio/volume/volume.cpp
index 9d815b19a..41b7dbcda 100644
--- a/app/node/audio/volume/volume.cpp
+++ b/app/node/audio/volume/volume.cpp
@@ -27,7 +27,7 @@ VolumeNode::VolumeNode()
samples_input_ = new NodeInput("samples_in", NodeParam::kSamples);
AddInput(samples_input_);
- volume_input_ = new NodeInput("volume_in", NodeParam::kFloat, 1);
+ volume_input_ = new NodeInput("volume_in", NodeParam::kFloat, 1.0);
volume_input_->set_property("min", 0.0);
volume_input_->set_property("view", "db");
AddInput(volume_input_);
diff --git a/app/node/generator/matrix/matrix.cpp b/app/node/generator/matrix/matrix.cpp
index bafa5d530..ee9ae0a31 100644
--- a/app/node/generator/matrix/matrix.cpp
+++ b/app/node/generator/matrix/matrix.cpp
@@ -30,10 +30,10 @@ OLIVE_NAMESPACE_ENTER
MatrixGenerator::MatrixGenerator()
{
- position_input_ = new NodeInput("pos_in", NodeParam::kVec2);
+ position_input_ = new NodeInput("pos_in", NodeParam::kVec2, QVector2D());
AddInput(position_input_);
- rotation_input_ = new NodeInput("rot_in", NodeParam::kFloat);
+ rotation_input_ = new NodeInput("rot_in", NodeParam::kFloat, 0.0f);
AddInput(rotation_input_);
scale_input_ = new NodeInput("scale_in", NodeParam::kVec2, QVector2D(1.0f, 1.0f));
@@ -48,7 +48,7 @@ MatrixGenerator::MatrixGenerator()
connect(uniform_scale_input_, &NodeInput::ValueChanged, this, &MatrixGenerator::UniformScaleChanged);
AddInput(uniform_scale_input_);
- anchor_input_ = new NodeInput("anchor_in", NodeParam::kVec2);
+ anchor_input_ = new NodeInput("anchor_in", NodeParam::kVec2, QVector2D());
AddInput(anchor_input_);
}
diff --git a/app/node/input.cpp b/app/node/input.cpp
index 17c41c02b..db1bbb5c9 100644
--- a/app/node/input.cpp
+++ b/app/node/input.cpp
@@ -386,9 +386,22 @@ void NodeInput::GetDependencies(QList &list, bool traverse, bool exclusi
QVariant NodeInput::GetDefaultValue() const
{
+ if (default_value_.isEmpty()) {
+ return QVariant();
+ }
+
return combine_track_values_into_normal_value(default_value_);
}
+QVariant NodeInput::GetDefaultValueForTrack(int track) const
+{
+ if (default_value_.isEmpty()) {
+ return QVariant();
+ }
+
+ return default_value_.at(track);
+}
+
QList NodeInput::GetDependencies(bool traverse, bool exclusive_only) const
{
QList list;
diff --git a/app/node/input.h b/app/node/input.h
index a8b776261..c9e1b1d8d 100644
--- a/app/node/input.h
+++ b/app/node/input.h
@@ -279,6 +279,8 @@ public:
QVariant GetDefaultValue() const;
+ QVariant GetDefaultValueForTrack(int track) const;
+
QList GetDependencies(bool traverse = true, bool exclusive_only = false) const;
QList GetExclusiveDependencies() const;
diff --git a/app/node/math/math/math.cpp b/app/node/math/math/math.cpp
index 9f2f4ca3e..149bcb08a 100644
--- a/app/node/math/math/math.cpp
+++ b/app/node/math/math/math.cpp
@@ -35,12 +35,12 @@ MathNode::MathNode()
method_in_->set_is_keyframable(false);
AddInput(method_in_);
- param_a_in_ = new NodeInput(QStringLiteral("param_a_in"), NodeParam::kFloat);
+ param_a_in_ = new NodeInput(QStringLiteral("param_a_in"), NodeParam::kFloat, 0.0);
param_a_in_->set_property(QStringLiteral("decimalplaces"), 8);
param_a_in_->set_property(QStringLiteral("autotrim"), true);
AddInput(param_a_in_);
- param_b_in_ = new NodeInput(QStringLiteral("param_b_in"), NodeParam::kFloat);
+ param_b_in_ = new NodeInput(QStringLiteral("param_b_in"), NodeParam::kFloat, 0.0);
param_b_in_->set_property(QStringLiteral("decimalplaces"), 8);
param_b_in_->set_property(QStringLiteral("autotrim"), true);
AddInput(param_b_in_);
diff --git a/app/node/math/trigonometry/trigonometry.cpp b/app/node/math/trigonometry/trigonometry.cpp
index 7ab8ddb3a..9352fded8 100644
--- a/app/node/math/trigonometry/trigonometry.cpp
+++ b/app/node/math/trigonometry/trigonometry.cpp
@@ -29,7 +29,7 @@ TrigonometryNode::TrigonometryNode()
method_in_->set_is_keyframable(false);
AddInput(method_in_);
- x_in_ = new NodeInput(QStringLiteral("x_in"), NodeParam::kFloat);
+ x_in_ = new NodeInput(QStringLiteral("x_in"), NodeParam::kFloat, 0.0);
AddInput(x_in_);
}
diff --git a/app/panel/viewer/viewerbase.cpp b/app/panel/viewer/viewerbase.cpp
index 9f9e4c84e..fd1a066c5 100644
--- a/app/panel/viewer/viewerbase.cpp
+++ b/app/panel/viewer/viewerbase.cpp
@@ -111,4 +111,11 @@ void ViewerPanelBase::CreateScopePanel(ScopePanel::Type type)
vw->ForceUpdate();
}
+void ViewerPanelBase::closeEvent(QCloseEvent *e)
+{
+ static_cast(GetTimeBasedWidget())->Pause();
+
+ TimeBasedPanel::closeEvent(e);
+}
+
OLIVE_NAMESPACE_EXIT
diff --git a/app/panel/viewer/viewerbase.h b/app/panel/viewer/viewerbase.h
index ed4c5651a..99340a983 100644
--- a/app/panel/viewer/viewerbase.h
+++ b/app/panel/viewer/viewerbase.h
@@ -62,6 +62,8 @@ public slots:
protected:
void CreateScopePanel(ScopePanel::Type type);
+ virtual void closeEvent(QCloseEvent* e) override;
+
};
OLIVE_NAMESPACE_EXIT
diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp
index 3f9933218..ab1f20b49 100644
--- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp
+++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp
@@ -66,6 +66,7 @@ const QList &NodeParamViewWidgetBridge::widgets() const
void NodeParamViewWidgetBridge::CreateWidgets()
{
+
if (input_->IsArray()) {
NodeParamViewArrayWidget* w = new NodeParamViewArrayWidget(static_cast(input_));
@@ -91,6 +92,7 @@ void NodeParamViewWidgetBridge::CreateWidgets()
case NodeParam::kInt:
{
IntegerSlider* slider = new IntegerSlider();
+ slider->SetDefaultValue(input_->GetDefaultValue());
widgets_.append(slider);
connect(slider, &IntegerSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
break;
@@ -363,6 +365,7 @@ void NodeParamViewWidgetBridge::CreateSliders(int count)
{
for (int i=0;iSetDefaultValue(input_->GetDefaultValueForTrack(i));
widgets_.append(fs);
connect(fs, &FloatSlider::ValueChanged, this, &NodeParamViewWidgetBridge::WidgetCallback);
}
diff --git a/app/widget/slider/sliderbase.cpp b/app/widget/slider/sliderbase.cpp
index 4e41119bf..5de18603d 100644
--- a/app/widget/slider/sliderbase.cpp
+++ b/app/widget/slider/sliderbase.cpp
@@ -45,12 +45,13 @@ SliderBase::SliderBase(Mode mode, QWidget *parent) :
editor_ = new FocusableLineEdit(this);
addWidget(editor_);
- connect(label_, SIGNAL(drag_start()), this, SLOT(LabelPressed()));
- connect(label_, SIGNAL(dragged(int)), this, SLOT(LabelDragged(int)));
- connect(label_, SIGNAL(drag_stop()), this, SLOT(LabelClicked()));
- connect(label_, SIGNAL(focused()), this, SLOT(LabelClicked()));
- connect(editor_, SIGNAL(Confirmed()), this, SLOT(LineEditConfirmed()));
- connect(editor_, SIGNAL(Cancelled()), this, SLOT(LineEditCancelled()));
+ connect(label_, &SliderLabel::drag_start, this, &SliderBase::LabelPressed);
+ connect(label_, &SliderLabel::dragged, this, &SliderBase::LabelDragged);
+ connect(label_, &SliderLabel::drag_stop, this, &SliderBase::LabelClicked);
+ connect(label_, &SliderLabel::focused, this, &SliderBase::LabelClicked);
+ connect(label_, &SliderLabel::RequestReset, this, &SliderBase::ResetValue);
+ connect(editor_, &FocusableLineEdit::Confirmed, this, &SliderBase::LineEditConfirmed);
+ connect(editor_, &FocusableLineEdit::Cancelled, this, &SliderBase::LineEditCancelled);
// Set valid cursor based on mode
switch (mode_) {
@@ -137,6 +138,11 @@ void SliderBase::SetValue(const QVariant &v)
UpdateLabel(value_);
}
+void SliderBase::SetDefaultValue(const QVariant &v)
+{
+ default_value_ = v;
+}
+
void SliderBase::SetMinimumInternal(const QVariant &v)
{
min_value_ = v;
@@ -241,7 +247,6 @@ void SliderBase::LabelClicked()
emit ValueChanged(value_);
} else {
// This was a simple click
-
// Load label's text into editor
editor_->setText(ValueToString(value_));
@@ -327,4 +332,12 @@ void SliderBase::LineEditCancelled()
label_->blockSignals(false);
}
+void SliderBase::ResetValue()
+{
+ if (!default_value_.isNull()) {
+ SetValue(default_value_);
+ emit ValueChanged(value_);
+ }
+}
+
OLIVE_NAMESPACE_EXIT
diff --git a/app/widget/slider/sliderbase.h b/app/widget/slider/sliderbase.h
index 2a4b8899f..316e82f37 100644
--- a/app/widget/slider/sliderbase.h
+++ b/app/widget/slider/sliderbase.h
@@ -45,6 +45,8 @@ public:
void SetAlignment(Qt::Alignment alignment);
+ void SetDefaultValue(const QVariant& v);
+
bool IsTristate() const;
void SetTristate();
@@ -89,6 +91,7 @@ private:
FocusableLineEdit* editor_;
QVariant value_;
+ QVariant default_value_;
bool has_min_;
QVariant min_value_;
@@ -120,6 +123,8 @@ private slots:
void LineEditConfirmed();
void LineEditCancelled();
+
+ void ResetValue();
};
OLIVE_NAMESPACE_EXIT
diff --git a/app/widget/slider/sliderlabel.cpp b/app/widget/slider/sliderlabel.cpp
index aee4a8409..79f077757 100644
--- a/app/widget/slider/sliderlabel.cpp
+++ b/app/widget/slider/sliderlabel.cpp
@@ -31,7 +31,8 @@
OLIVE_NAMESPACE_ENTER
SliderLabel::SliderLabel(QWidget *parent) :
- QLabel(parent)
+ QLabel(parent),
+ dragging_(false)
{
QPalette p = palette();
@@ -53,51 +54,70 @@ SliderLabel::SliderLabel(QWidget *parent) :
setFocusPolicy(Qt::TabFocus);
}
-void SliderLabel::mousePressEvent(QMouseEvent *)
+void SliderLabel::mousePressEvent(QMouseEvent *e)
{
- emit drag_start();
+ if (e->modifiers() & Qt::AltModifier) {
+
+ emit RequestReset();
+
+ } else {
#if defined(Q_OS_MAC)
- CGAssociateMouseAndMouseCursorPosition(false);
- CGDisplayHideCursor(kCGDirectMainDisplay);
- CGGetLastMouseDelta(nullptr, nullptr);
+ CGAssociateMouseAndMouseCursorPosition(false);
+ CGDisplayHideCursor(kCGDirectMainDisplay);
+ CGGetLastMouseDelta(nullptr, nullptr);
#else
- drag_start_ = QCursor::pos();
+ drag_start_ = QCursor::pos();
- static_cast(QApplication::instance())->setOverrideCursor(Qt::BlankCursor);
+ static_cast(QApplication::instance())->setOverrideCursor(Qt::BlankCursor);
#endif
+
+ emit drag_start();
+
+ dragging_ = true;
+
+ }
}
void SliderLabel::mouseMoveEvent(QMouseEvent *)
{
- int32_t x_mvmt, y_mvmt;
+ if (dragging_) {
- // Keep cursor in the same position
+ int32_t x_mvmt, y_mvmt;
+
+ // Keep cursor in the same position
#if defined(Q_OS_MAC)
- CGGetLastMouseDelta(&x_mvmt, &y_mvmt);
+ CGGetLastMouseDelta(&x_mvmt, &y_mvmt);
#else
- QPoint current_pos = QCursor::pos();
+ QPoint current_pos = QCursor::pos();
- x_mvmt = current_pos.x() - drag_start_.x();
- y_mvmt = drag_start_.y() - current_pos.y();
+ x_mvmt = current_pos.x() - drag_start_.x();
+ y_mvmt = drag_start_.y() - current_pos.y();
- QCursor::setPos(drag_start_);
+ QCursor::setPos(drag_start_);
#endif
- emit dragged(x_mvmt + y_mvmt);
+ emit dragged(x_mvmt + y_mvmt);
+
+ }
}
void SliderLabel::mouseReleaseEvent(QMouseEvent *)
{
+ if (dragging_) {
+
#if defined(Q_OS_MAC)
- CGAssociateMouseAndMouseCursorPosition(true);
- CGDisplayShowCursor(kCGDirectMainDisplay);
+ CGAssociateMouseAndMouseCursorPosition(true);
+ CGDisplayShowCursor(kCGDirectMainDisplay);
#else
- static_cast(QApplication::instance())->restoreOverrideCursor();
+ static_cast(QApplication::instance())->restoreOverrideCursor();
#endif
- // Emit a clicked signal
- emit drag_stop();
+ emit drag_stop();
+
+ dragging_ = false;
+
+ }
}
void SliderLabel::focusInEvent(QFocusEvent *event)
diff --git a/app/widget/slider/sliderlabel.h b/app/widget/slider/sliderlabel.h
index c83e86bfc..641caeac4 100644
--- a/app/widget/slider/sliderlabel.h
+++ b/app/widget/slider/sliderlabel.h
@@ -51,10 +51,12 @@ signals:
void focused();
+ void RequestReset();
+
private:
QPoint drag_start_;
- bool cancel_mm_event_;
+ bool dragging_;
};
diff --git a/app/widget/viewer/CMakeLists.txt b/app/widget/viewer/CMakeLists.txt
index 70df051ae..f745ca0d6 100644
--- a/app/widget/viewer/CMakeLists.txt
+++ b/app/widget/viewer/CMakeLists.txt
@@ -26,6 +26,9 @@ set(OLIVE_SOURCES
widget/viewer/viewer.cpp
widget/viewer/viewerdisplay.h
widget/viewer/viewerdisplay.cpp
+ widget/viewer/viewerplaybacktimer.h
+ widget/viewer/viewerplaybacktimer.cpp
+ widget/viewer/viewerqueue.h
widget/viewer/viewersafemargininfo.h
widget/viewer/viewersizer.h
widget/viewer/viewersizer.cpp
diff --git a/app/widget/viewer/footageviewer.cpp b/app/widget/viewer/footageviewer.cpp
index 3ee2569b0..b11acc76b 100644
--- a/app/widget/viewer/footageviewer.cpp
+++ b/app/widget/viewer/footageviewer.cpp
@@ -35,7 +35,7 @@ FootageViewerWidget::FootageViewerWidget(QWidget *parent) :
audio_node_ = new AudioInput();
viewer_node_ = new ViewerOutput();
- connect(main_gl_widget(), &ViewerDisplayWidget::DragStarted, this, &FootageViewerWidget::StartFootageDrag);
+ connect(display_widget(), &ViewerDisplayWidget::DragStarted, this, &FootageViewerWidget::StartFootageDrag);
controls_->SetAudioVideoDragButtonsVisible(true);
connect(controls_, &PlaybackControls::VideoPressed, this, &FootageViewerWidget::StartVideoDrag);
diff --git a/app/widget/viewer/viewer.cpp b/app/widget/viewer/viewer.cpp
index 5d8cf5e62..26e8f188c 100644
--- a/app/widget/viewer/viewer.cpp
+++ b/app/widget/viewer/viewer.cpp
@@ -63,15 +63,13 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
sizer_ = new ViewerSizer();
stack_->addWidget(sizer_);
- ViewerDisplayWidget* main_widget = new ViewerDisplayWidget();
- connect(main_widget, &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
- connect(main_widget, &ViewerDisplayWidget::CursorColor, this, &ViewerWidget::CursorColor);
- connect(main_widget, &ViewerDisplayWidget::LoadedBuffer, this, &ViewerWidget::LoadedBuffer);
- connect(main_widget, &ViewerDisplayWidget::ColorProcessorChanged, this, &ViewerWidget::ColorProcessorChanged);
- connect(main_widget, &ViewerDisplayWidget::ColorManagerChanged, this, &ViewerWidget::ColorManagerChanged);
- connect(sizer_, &ViewerSizer::RequestMatrix, main_widget, &ViewerDisplayWidget::SetMatrix);
- sizer_->SetWidget(main_widget);
- gl_widgets_.append(main_widget);
+ display_widget_ = new ViewerDisplayWidget();
+ connect(display_widget_, &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
+ connect(display_widget_, &ViewerDisplayWidget::CursorColor, this, &ViewerWidget::CursorColor);
+ connect(display_widget_, &ViewerDisplayWidget::ColorProcessorChanged, this, &ViewerWidget::ColorProcessorChanged);
+ connect(display_widget_, &ViewerDisplayWidget::ColorManagerChanged, this, &ViewerWidget::ColorManagerChanged);
+ connect(sizer_, &ViewerSizer::RequestMatrix, display_widget_, &ViewerDisplayWidget::SetMatrix);
+ sizer_->SetWidget(display_widget_);
// Create waveform view when audio is connected and video isn't
waveform_view_ = new AudioWaveformView();
@@ -117,6 +115,15 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
SetAutoMaxScrollBar(true);
}
+ViewerWidget::~ViewerWidget()
+{
+ QList windows = windows_;
+
+ foreach (ViewerWindow* window, windows) {
+ delete window;
+ }
+}
+
void ViewerWidget::TimeChangedEvent(const int64_t &i)
{
if (!time_changed_from_timer_) {
@@ -133,7 +140,7 @@ void ViewerWidget::TimeChangedEvent(const int64_t &i)
PushScrubbedAudio();
- main_gl_widget()->SetTime(time_set);
+ display_widget_->SetTime(time_set);
}
last_time_ = i;
@@ -170,8 +177,9 @@ void ViewerWidget::ConnectNodeInternal(ViewerOutput *n)
using_manager = nullptr;
}
- foreach (ViewerDisplayWidget* glw, gl_widgets_) {
- glw->ConnectColorManager(using_manager);
+ display_widget_->ConnectColorManager(using_manager);
+ foreach (ViewerWindow* window, windows_) {
+ window->display_widget()->ConnectColorManager(using_manager);
}
divider_ = CalculateDivider();
@@ -202,8 +210,9 @@ void ViewerWidget::DisconnectNodeInternal(ViewerOutput *n)
// Effectively disables the viewer and clears the state
SizeChangedSlot(0, 0);
- foreach (ViewerDisplayWidget* glw, gl_widgets_) {
- glw->DisconnectColorManager();
+ display_widget_->DisconnectColorManager();
+ foreach (ViewerWindow* window, windows_) {
+ window->display_widget()->DisconnectColorManager();
}
waveform_view_->ConnectTimelinePoints(nullptr);
@@ -236,14 +245,9 @@ void ViewerWidget::resizeEvent(QResizeEvent *event)
UpdateMinimumScale();
}
-const QList &ViewerWidget::gl_widgets() const
+ViewerDisplayWidget *ViewerWidget::display_widget() const
{
- return gl_widgets_;
-}
-
-ViewerDisplayWidget *ViewerWidget::main_gl_widget() const
-{
- return gl_widgets_.first();
+ return display_widget_;
}
void ViewerWidget::TogglePlayPause()
@@ -282,8 +286,9 @@ void ViewerWidget::SetOverrideSize(int width, int height)
void ViewerWidget::SetMatrix(const QMatrix4x4 &mat)
{
- foreach (ViewerDisplayWidget* glw, gl_widgets_) {
- glw->SetMatrix(mat);
+ display_widget_->SetMatrix(mat);
+ foreach (ViewerWindow* vw, windows_) {
+ vw->display_widget()->SetMatrix(mat);
}
}
@@ -306,19 +311,19 @@ void ViewerWidget::SetFullScreen(QScreen *screen)
ViewerWindow* vw = new ViewerWindow(this);
- vw->showFullScreen();
vw->setGeometry(screen->geometry());
- vw->gl_widget()->ConnectColorManager(main_gl_widget()->color_manager());
- main_gl_widget()->ConnectSibling(vw->gl_widget());
+ vw->showFullScreen();
+ vw->display_widget()->ConnectColorManager(color_manager());
connect(vw, &ViewerWindow::destroyed, this, &ViewerWidget::WindowAboutToClose);
- connect(vw->gl_widget(), &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
+ connect(vw->display_widget(), &ViewerDisplayWidget::customContextMenuRequested, this, &ViewerWidget::ShowContextMenu);
if (GetConnectedNode()) {
vw->SetResolution(GetConnectedNode()->video_params().width(), GetConnectedNode()->video_params().height());
}
+ vw->display_widget()->SetImage(display_widget_->last_loaded_buffer());
+
windows_.append(vw);
- gl_widgets_.append(vw->gl_widget());
}
void ViewerWidget::ForceUpdate()
@@ -334,26 +339,45 @@ VideoRenderBackend *ViewerWidget::video_renderer() const
ColorManager *ViewerWidget::color_manager() const
{
- return main_gl_widget()->color_manager();
+ return display_widget_->color_manager();
}
void ViewerWidget::SetGizmos(Node *node)
{
- main_gl_widget()->SetTimeTarget(GetConnectedNode());
- main_gl_widget()->SetGizmos(node);
+ display_widget_->SetTimeTarget(GetConnectedNode());
+ display_widget_->SetGizmos(node);
}
void ViewerWidget::UpdateTextureFromNode(const rational& time)
{
- if (!GetConnectedNode() || time >= GetConnectedNode()->Length()) {
- main_gl_widget()->SetImage(QString());
- video_renderer_->UpdateLastRequestedTime(time);
- } else {
- QString frame_fn = video_renderer_->GetCachedFrame(time);
+ {
+ QMutexLocker locker(playback_queue_.lock());
+
+ while (!playback_queue_.isEmpty()) {
+ const ViewerPlaybackFrame& pf = playback_queue_.first();
+
+ if (pf.timestamp == time) {
+ // Frame was in queue, no need to decode anything
+ SetDisplayImage(pf.frame, true);
+ QtConcurrent::run(this, &ViewerWidget::FillPlaybackQueue);
+ return;
+ } else {
+ playback_queue_.removeFirst();
+ }
+ }
+ }
+
+ // Frame was not in queue, will require decoding
+ if (FrameExistsAtTime(time)) {
+ QString frame_fn = GetCachedFilenameFromTime(time);
if (!frame_fn.isEmpty()) {
- main_gl_widget()->SetImage(frame_fn);
+ FramePtr f = DecodeCachedImage(frame_fn);
+ SetDisplayImage(f, false);
}
+ } else {
+ SetDisplayImage(nullptr, false);
+ video_renderer_->UpdateLastRequestedTime(time);
}
}
@@ -372,11 +396,21 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
QString audio_fn = audio_renderer_->CachePathName();
if (!audio_fn.isEmpty()) {
AudioManager::instance()->SetOutputParams(audio_renderer_->params());
- AudioManager::instance()->StartOutput(audio_fn, audio_renderer_->params().time_to_bytes(GetTime()), playback_speed_);
+ AudioManager::instance()->StartOutput(audio_fn,
+ audio_renderer_->params().time_to_bytes(GetTime()),
+ playback_speed_);
}
- start_msec_ = QDateTime::currentMSecsSinceEpoch();
- start_timestamp_ = ruler()->GetTime();
+ int64_t start_time = ruler()->GetTime();
+
+ playback_queue_next_frame_ = start_time;
+ FillPlaybackQueue();
+
+ playback_timer_.Start(start_time, playback_speed_, timebase_dbl());
+
+ foreach (ViewerWindow* window, windows_) {
+ window->Play(start_time, playback_speed_, timebase());
+ }
controls_->ShowPauseButton();
@@ -385,7 +419,7 @@ void ViewerWidget::PlayInternal(int speed, bool in_to_out_only)
if (playback_is_audio_only_) {
connect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate);
} else {
- connect(main_gl_widget(), &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
+ connect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
}
}
@@ -415,7 +449,7 @@ int ViewerWidget::CalculateDivider()
{
if (GetConnectedNode() && Config::Current()["AutoSelectDivider"].toBool()) {
int long_side_of_video = qMax(GetConnectedNode()->video_params().width(), GetConnectedNode()->video_params().height());
- int long_side_of_widget = qMax(main_gl_widget()->width(), main_gl_widget()->height());
+ int long_side_of_widget = qMax(display_widget_->width(), display_widget_->height());
return qMax(1, long_side_of_video / long_side_of_widget);
}
@@ -442,6 +476,113 @@ void ViewerWidget::SetColorTransform(const ColorTransform &transform, ViewerDisp
sender->SetColorTransform(transform);
}
+void ViewerWidget::FillPlaybackQueue()
+{
+ playback_queue_.lock()->lock();
+ foreach (ViewerWindow* window, windows_) {
+ window->queue()->lock()->lock();
+ }
+
+ while (playback_queue_.size() < 8) {
+ // Load frame from cache
+ FramePtr frame = nullptr;
+
+ rational rtime = Timecode::timestamp_to_time(playback_queue_next_frame_,
+ timebase());
+
+ QString frame_fn = GetCachedFilenameFromTime(rtime);
+
+ if (!frame_fn.isEmpty()) {
+ frame = DecodeCachedImage(frame_fn);
+ }
+
+ ViewerPlaybackFrame f = {rtime, frame};
+
+ playback_queue_.append(f);
+
+ foreach (ViewerWindow* window, windows_) {
+ window->queue()->append(f);
+ }
+
+ if (!playback_speed_) {
+ break;
+ }
+
+ playback_queue_next_frame_ += playback_speed_;
+ }
+
+ foreach (ViewerWindow* window, windows_) {
+ window->queue()->lock()->unlock();
+ }
+ playback_queue_.lock()->unlock();
+}
+
+QString ViewerWidget::GetCachedFilenameFromTime(const rational &time)
+{
+ if (FrameExistsAtTime(time)) {
+ return video_renderer_->GetCachedFrame(time);
+ } else {
+ return QString();
+ }
+}
+
+bool ViewerWidget::FrameExistsAtTime(const rational &time)
+{
+ return GetConnectedNode() && time < GetConnectedNode()->Length();
+}
+
+FramePtr ViewerWidget::DecodeCachedImage(const QString &fn)
+{
+ FramePtr frame = nullptr;
+
+ if (!fn.isEmpty() && QFileInfo::exists(fn)) {
+ auto input = OIIO::ImageInput::open(fn.toStdString());
+
+ if (input) {
+
+ PixelFormat::Format image_format = PixelFormat::OIIOFormatToOliveFormat(input->spec().format,
+ input->spec().nchannels == kRGBAChannels);
+
+ frame = Frame::Create();
+
+ frame->set_video_params(VideoRenderingParams(input->spec().width,
+ input->spec().height,
+ image_format));
+
+ frame->allocate();
+
+ input->read_image(input->spec().format,
+ frame->data(),
+ OIIO::AutoStride,
+ frame->linesize_bytes());
+
+ input->close();
+
+#if OIIO_VERSION < 10903
+ OIIO::ImageInput::destroy(input);
+#endif
+
+ } else {
+ qWarning() << "OIIO Error:" << OIIO::geterror().c_str();
+ }
+ }
+
+ return frame;
+}
+
+void ViewerWidget::SetDisplayImage(FramePtr frame, bool main_only)
+{
+ display_widget_->SetImage(frame);
+
+ if (!main_only) {
+ foreach (ViewerWindow* vw, windows_) {
+ vw->display_widget()->SetImage(frame);
+ }
+ }
+
+ emit LoadedBuffer(frame.get());
+}
+
void ViewerWidget::UpdateStack()
{
if (GetConnectedNode()
@@ -515,10 +656,7 @@ void ViewerWidget::ContextMenuSetCustomSafeMargins()
void ViewerWidget::WindowAboutToClose()
{
- ViewerWindow* vw = static_cast(sender());
-
- windows_.removeAll(vw);
- gl_widgets_.removeAll(vw->gl_widget());
+ windows_.removeOne(static_cast(sender()));
}
void ViewerWidget::ContextMenuScopeTriggered(QAction *action)
@@ -528,9 +666,7 @@ void ViewerWidget::ContextMenuScopeTriggered(QAction *action)
void ViewerWidget::RendererGeneratedFrame(FramePtr f)
{
- foreach (ViewerDisplayWidget* glw, gl_widgets_) {
- glw->SetImageFromLoadBuffer(f.get());
- }
+ SetDisplayImage(f, false);
}
void ViewerWidget::UpdateRendererParameters()
@@ -551,7 +687,7 @@ void ViewerWidget::UpdateRendererParameters()
video_renderer_->InvalidateCache(TimeRange(0, GetConnectedNode()->Length()), nullptr);
}
- main_gl_widget()->SetVideoParams(vparam);
+ display_widget_->SetVideoParams(vparam);
AudioRenderingParams aparam(GetConnectedNode()->audio_params(),
SampleFormat::kInternalFormat);
@@ -708,7 +844,17 @@ void ViewerWidget::Pause()
if (playback_is_audio_only_) {
disconnect(AudioManager::instance(), &AudioManager::OutputNotified, this, &ViewerWidget::PlaybackTimerUpdate);
} else {
- disconnect(main_gl_widget(), &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
+ disconnect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWidget::PlaybackTimerUpdate);
+ }
+
+ foreach (ViewerWindow* window, windows_) {
+ window->Pause();
+ }
+
+ {
+ playback_queue_.lock()->lock();
+ playback_queue_.clear();
+ playback_queue_.lock()->unlock();
}
}
}
@@ -754,13 +900,15 @@ void ViewerWidget::ShuttleRight()
void ViewerWidget::SetColorTransform(const ColorTransform &transform)
{
- SetColorTransform(transform, main_gl_widget());
+ SetColorTransform(transform, display_widget_);
}
void ViewerWidget::SetSignalCursorColorEnabled(bool e)
{
- foreach (ViewerDisplayWidget* glw, gl_widgets_) {
- glw->SetSignalCursorColorEnabled(e);
+ display_widget_->SetSignalCursorColorEnabled(e);
+
+ foreach (ViewerWindow* vw, windows_) {
+ vw->display_widget()->SetSignalCursorColorEnabled(e);
}
}
@@ -776,11 +924,7 @@ void ViewerWidget::TimebaseChangedEvent(const rational &timebase)
void ViewerWidget::PlaybackTimerUpdate()
{
- int64_t real_time = QDateTime::currentMSecsSinceEpoch() - start_msec_;
-
- int64_t frames_since_start = qRound(static_cast(real_time) / (timebase_dbl() * 1000));
-
- int64_t current_time = start_timestamp_ + frames_since_start * playback_speed_;
+ int64_t current_time = playback_timer_.GetTimestampNow();
int64_t min_time, max_time;
diff --git a/app/widget/viewer/viewer.h b/app/widget/viewer/viewer.h
index 164f77122..4fcd386bf 100644
--- a/app/widget/viewer/viewer.h
+++ b/app/widget/viewer/viewer.h
@@ -36,6 +36,8 @@
#include "render/backend/opengl/opengltexture.h"
#include "render/backend/audio/audiobackend.h"
#include "viewerdisplay.h"
+#include "viewerplaybacktimer.h"
+#include "viewerqueue.h"
#include "viewersizer.h"
#include "viewerwindow.h"
#include "widget/playbackcontrols/playbackcontrols.h"
@@ -52,6 +54,8 @@ class ViewerWidget : public TimeBasedWidget
public:
ViewerWidget(QWidget* parent = nullptr);
+ virtual ~ViewerWidget() override;
+
void SetPlaybackControlsEnabled(bool enabled);
void SetTimeRulerEnabled(bool enabled);
@@ -116,7 +120,7 @@ signals:
void CursorColor(const Color& reference, const Color& display);
/**
- * @brief Wrapper for ViewerGLWidget::LoadedBuffer()
+ * @brief Signal emitted when a new frame is loaded
*/
void LoadedBuffer(Frame* load_buffer);
@@ -154,8 +158,7 @@ protected:
PlaybackControls* controls_;
- const QList &gl_widgets() const;
- ViewerDisplayWidget* main_gl_widget() const;
+ ViewerDisplayWidget* display_widget() const;
private:
void UpdateTimeInternal(int64_t i);
@@ -172,14 +175,21 @@ private:
void SetColorTransform(const ColorTransform& transform, ViewerDisplayWidget* sender);
+ void FillPlaybackQueue();
+
+ QString GetCachedFilenameFromTime(const rational& time);
+
+ bool FrameExistsAtTime(const rational& time);
+
+ FramePtr DecodeCachedImage(const QString& fn);
+
+ void SetDisplayImage(FramePtr frame, bool main_only);
+
QStackedWidget* stack_;
ViewerSizer* sizer_;
- qint64 start_msec_;
- int64_t start_timestamp_;
-
- int playback_speed_;
+ QAtomicInt playback_speed_;
qint64 frame_cache_job_time_;
@@ -201,10 +211,15 @@ private:
QList windows_;
- QList gl_widgets_;
+ ViewerDisplayWidget* display_widget_;
ViewerDisplayWidget* context_menu_widget_;
+ ViewerPlaybackTimer playback_timer_;
+
+ ViewerQueue playback_queue_;
+ int64_t playback_queue_next_frame_;
+
private slots:
void PlaybackTimerUpdate();
diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp
index 3d86eb5d6..6fcc8e0e2 100644
--- a/app/widget/viewer/viewerdisplay.cpp
+++ b/app/widget/viewer/viewerdisplay.cpp
@@ -30,6 +30,7 @@
#include
#include "common/define.h"
+#include "common/functiontimer.h"
#include "gizmotraverser.h"
#include "render/backend/opengl/openglrenderfunctions.h"
#include "render/backend/opengl/openglshader.h"
@@ -43,10 +44,10 @@ bool ViewerDisplayWidget::nouveau_check_done_ = false;
ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) :
ManagedDisplayWidget(parent),
- has_image_(false),
signal_cursor_color_(false),
gizmos_(nullptr),
- gizmo_click_(false)
+ gizmo_click_(false),
+ last_loaded_buffer_(nullptr)
{
}
@@ -61,81 +62,24 @@ void ViewerDisplayWidget::SetMatrix(const QMatrix4x4 &mat)
update();
}
-void ViewerDisplayWidget::SetImage(const QString &fn)
-{
- has_image_ = false;
-
- if (!fn.isEmpty() && QFileInfo::exists(fn)) {
- auto input = OIIO::ImageInput::open(fn.toStdString());
-
- if (input) {
-
- PixelFormat::Format image_format = PixelFormat::OIIOFormatToOliveFormat(input->spec().format,
- input->spec().nchannels == kRGBAChannels);
-
- // Ensure the following texture operations are done in our context (in case we're in a separate window for instance)
- makeCurrent();
-
- if (!texture_.IsCreated()
- || texture_.width() != input->spec().width
- || texture_.height() != input->spec().height
- || texture_.format() != image_format) {
- load_buffer_.destroy();
- texture_.Destroy();
-
- load_buffer_.set_video_params(VideoRenderingParams(input->spec().width, input->spec().height, image_format));
- load_buffer_.allocate();
-
- texture_.Create(context(), VideoRenderingParams(input->spec().width, input->spec().height, image_format));
- }
-
- input->read_image(input->spec().format, load_buffer_.data(), OIIO::AutoStride, load_buffer_.linesize_bytes());
- input->close();
-
- texture_.Upload(&load_buffer_);
-
- doneCurrent();
-
- emit LoadedBuffer(&load_buffer_);
-
- has_image_ = true;
-
-#if OIIO_VERSION < 10903
- OIIO::ImageInput::destroy(input);
-#endif
-
- } else {
- qWarning() << "OIIO Error:" << OIIO::geterror().c_str();
- }
- }
-
- update();
-
- if (has_image_) {
- emit LoadedBuffer(&load_buffer_);
- } else {
- emit LoadedBuffer(nullptr);
- }
-}
-
void ViewerDisplayWidget::SetSignalCursorColorEnabled(bool e)
{
signal_cursor_color_ = e;
setMouseTracking(e);
}
-void ViewerDisplayWidget::SetImageFromLoadBuffer(Frame *in_buffer)
+void ViewerDisplayWidget::SetImage(FramePtr in_buffer)
{
- has_image_ = in_buffer;
+ last_loaded_buffer_ = in_buffer;
- if (has_image_) {
+ if (last_loaded_buffer_) {
makeCurrent();
if (!texture_.IsCreated()
|| texture_.width() != in_buffer->width()
|| texture_.height() != in_buffer->height()
|| texture_.format() != in_buffer->format()) {
- texture_.Create(context(), in_buffer->video_params(), in_buffer->data(), load_buffer_.linesize_pixels());
+ texture_.Create(context(), in_buffer->video_params(), in_buffer->data(), in_buffer->linesize_pixels());
} else {
texture_.Upload(in_buffer);
}
@@ -146,12 +90,6 @@ void ViewerDisplayWidget::SetImageFromLoadBuffer(Frame *in_buffer)
update();
}
-void ViewerDisplayWidget::ConnectSibling(ViewerDisplayWidget *sibling)
-{
- connect(this, &ViewerDisplayWidget::LoadedBuffer, sibling, &ViewerDisplayWidget::SetImageFromLoadBuffer, Qt::QueuedConnection);
- sibling->SetImageFromLoadBuffer(&load_buffer_);
-}
-
const ViewerSafeMarginInfo &ViewerDisplayWidget::GetSafeMargin() const
{
return safe_margin_;
@@ -189,6 +127,11 @@ void ViewerDisplayWidget::SetTime(const rational &time)
}
}
+FramePtr ViewerDisplayWidget::last_loaded_buffer() const
+{
+ return last_loaded_buffer_;
+}
+
void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event)
{
if (gizmos_
@@ -215,17 +158,17 @@ void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event)
if (signal_cursor_color_) {
Color reference, display;
- if (has_image_) {
+ if (last_loaded_buffer_) {
QVector3D pixel_pos(static_cast(event->x()) / static_cast(width()) * 2.0f - 1.0f,
static_cast(event->y()) / static_cast(height()) * 2.0f - 1.0f,
0);
pixel_pos = pixel_pos * matrix_.inverted();
- int frame_x = qRound((pixel_pos.x() + 1.0f) * 0.5f * load_buffer_.width());
- int frame_y = qRound((pixel_pos.y() + 1.0f) * 0.5f * load_buffer_.height());
+ int frame_x = qRound((pixel_pos.x() + 1.0f) * 0.5f * last_loaded_buffer_->width());
+ int frame_y = qRound((pixel_pos.y() + 1.0f) * 0.5f * last_loaded_buffer_->height());
- reference = load_buffer_.get_pixel(frame_x, frame_y);
+ reference = last_loaded_buffer_->get_pixel(frame_x, frame_y);
display = color_service()->ConvertColor(reference);
}
@@ -277,7 +220,7 @@ void ViewerDisplayWidget::paintGL()
f->glClear(GL_COLOR_BUFFER_BIT);
// We only draw if we have a pipeline
- if (has_image_ && color_service()) {
+ if (last_loaded_buffer_ && color_service()) {
// Bind retrieved texture
f->glBindTexture(GL_TEXTURE_2D, texture_.texture());
diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h
index 996758718..0f0d2b81c 100644
--- a/app/widget/viewer/viewerdisplay.h
+++ b/app/widget/viewer/viewerdisplay.h
@@ -66,15 +66,8 @@ public:
virtual ~ViewerDisplayWidget() override;
- /**
- * @brief Set an image to load and display on screen
- */
- void SetImage(const QString& fn);
-
const QMatrix4x4& GetMatrix();
- void ConnectSibling(ViewerDisplayWidget* sibling);
-
const ViewerSafeMarginInfo& GetSafeMargin() const;
void SetSafeMargins(const ViewerSafeMarginInfo& safe_margin);
@@ -82,6 +75,8 @@ public:
void SetVideoParams(const VideoRenderingParams& params);
void SetTime(const rational& time);
+ FramePtr last_loaded_buffer() const;
+
public slots:
/**
* @brief Set the transformation matrix to draw with
@@ -105,7 +100,7 @@ public slots:
* If there are multiple ViewerGLWidgets showing the same thing, this is faster than decoding the image from file
* each time.
*/
- void SetImageFromLoadBuffer(Frame* in_buffer);
+ void SetImage(FramePtr in_buffer);
signals:
/**
@@ -118,15 +113,6 @@ signals:
*/
void CursorColor(const Color& reference, const Color& display);
- /**
- * @brief Signal emitted when a buffer is loaded from file into memory
- *
- * This buffer will be the direct output of the renderer in reference space in CPU memory.
- *
- * Connect this to the SetImageFromLoadBuffer() slot of another ViewerGLWidget to show the same thing
- */
- void LoadedBuffer(Frame* load_buffer);
-
protected:
/**
* @brief Override the mouse press event for the DragStarted() signal and gizmos
@@ -174,17 +160,10 @@ private:
*/
QMatrix4x4 matrix_;
- /**
- * @brief Buffer to load images into RAM before sending them to the display
- */
- Frame load_buffer_;
-
#ifdef Q_OS_LINUX
static bool nouveau_check_done_;
#endif
- bool has_image_;
-
bool signal_cursor_color_;
ViewerSafeMarginInfo safe_margin_;
@@ -197,6 +176,8 @@ private:
rational time_;
+ FramePtr last_loaded_buffer_;
+
private slots:
/**
* @brief Slot to connect just before the OpenGL context is destroyed to clean up resources
diff --git a/app/widget/viewer/viewerplaybacktimer.cpp b/app/widget/viewer/viewerplaybacktimer.cpp
new file mode 100644
index 000000000..e07e925c9
--- /dev/null
+++ b/app/widget/viewer/viewerplaybacktimer.cpp
@@ -0,0 +1,44 @@
+/***
+
+ 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 .
+
+***/
+
+#include "viewerplaybacktimer.h"
+
+#include
+
+OLIVE_NAMESPACE_ENTER
+
+void ViewerPlaybackTimer::Start(const int64_t &start_timestamp, const int &playback_speed, const double &timebase)
+{
+ start_msec_ = QDateTime::currentMSecsSinceEpoch();
+ start_timestamp_ = start_timestamp;
+ playback_speed_ = playback_speed;
+ timebase_ = timebase;
+}
+
+int64_t ViewerPlaybackTimer::GetTimestampNow() const
+{
+ int64_t real_time = QDateTime::currentMSecsSinceEpoch() - start_msec_;
+
+ int64_t frames_since_start = qRound(static_cast(real_time) / (timebase_ * 1000));
+
+ return start_timestamp_ + frames_since_start * playback_speed_;
+}
+
+OLIVE_NAMESPACE_EXIT
diff --git a/app/widget/viewer/viewerplaybacktimer.h b/app/widget/viewer/viewerplaybacktimer.h
new file mode 100644
index 000000000..a4669a571
--- /dev/null
+++ b/app/widget/viewer/viewerplaybacktimer.h
@@ -0,0 +1,48 @@
+/***
+
+ 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 .
+
+***/
+
+#ifndef VIEWERPLAYBACKTIMER_H
+#define VIEWERPLAYBACKTIMER_H
+
+#include
+
+#include "common/define.h"
+
+OLIVE_NAMESPACE_ENTER
+
+class ViewerPlaybackTimer {
+public:
+ void Start(const int64_t& start_timestamp, const int& playback_speed, const double& timebase);
+
+ int64_t GetTimestampNow() const;
+
+private:
+ qint64 start_msec_;
+ int64_t start_timestamp_;
+
+ int playback_speed_;
+
+ double timebase_;
+
+};
+
+OLIVE_NAMESPACE_EXIT
+
+#endif // VIEWERPLAYBACKTIMER_H
diff --git a/app/widget/viewer/viewerqueue.h b/app/widget/viewer/viewerqueue.h
new file mode 100644
index 000000000..a08816051
--- /dev/null
+++ b/app/widget/viewer/viewerqueue.h
@@ -0,0 +1,50 @@
+/***
+
+ 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 .
+
+***/
+
+#ifndef VIEWERQUEUE_H
+#define VIEWERQUEUE_H
+
+#include
+
+#include "codec/frame.h"
+
+OLIVE_NAMESPACE_ENTER
+
+struct ViewerPlaybackFrame {
+ rational timestamp;
+ FramePtr frame;
+};
+
+class ViewerQueue : public QLinkedList {
+public:
+ ViewerQueue() = default;
+
+ QMutex* lock() {
+ return &queue_lock_;
+ }
+
+private:
+ QMutex queue_lock_;
+
+};
+
+OLIVE_NAMESPACE_EXIT
+
+#endif // VIEWERQUEUE_H
diff --git a/app/widget/viewer/viewerwindow.cpp b/app/widget/viewer/viewerwindow.cpp
index 3f92f997d..04ae55afb 100644
--- a/app/widget/viewer/viewerwindow.cpp
+++ b/app/widget/viewer/viewerwindow.cpp
@@ -23,6 +23,8 @@
#include
#include
+#include "common/timecodefunctions.h"
+
OLIVE_NAMESPACE_ENTER
ViewerWindow::ViewerWindow(QWidget *parent) :
@@ -32,13 +34,13 @@ ViewerWindow::ViewerWindow(QWidget *parent) :
layout->setMargin(0);
layout->setSpacing(0);
- gl_widget_ = new ViewerDisplayWidget();
- layout->addWidget(gl_widget_);
+ display_widget_ = new ViewerDisplayWidget();
+ layout->addWidget(display_widget_);
}
-ViewerDisplayWidget *ViewerWindow::gl_widget() const
+ViewerDisplayWidget *ViewerWindow::display_widget() const
{
- return gl_widget_;
+ return display_widget_;
}
void ViewerWindow::SetResolution(int width, int height)
@@ -57,7 +59,26 @@ void ViewerWindow::SetResolution(int width, int height)
mat.scale(1.0f, window_ar / image_ar, 1.0f);
}
- gl_widget_->SetMatrix(mat);
+ display_widget_->SetMatrix(mat);
+}
+
+void ViewerWindow::Play(const int64_t& start_timestamp, const int& playback_speed, const rational &timebase)
+{
+ timer_.Start(start_timestamp, playback_speed, timebase.toDouble());
+
+ playback_timebase_ = timebase;
+
+ connect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWindow::UpdateFromQueue);
+
+ display_widget_->update();
+}
+
+void ViewerWindow::Pause()
+{
+ disconnect(display_widget_, &ViewerDisplayWidget::frameSwapped, this, &ViewerWindow::UpdateFromQueue);
+
+ QMutexLocker locker(queue_.lock());
+ queue_.clear();
}
void ViewerWindow::keyPressEvent(QKeyEvent *e)
@@ -76,4 +97,24 @@ void ViewerWindow::closeEvent(QCloseEvent *e)
deleteLater();
}
+void ViewerWindow::UpdateFromQueue()
+{
+ int64_t t = timer_.GetTimestampNow();
+
+ rational time = Timecode::timestamp_to_time(t, playback_timebase_);
+
+ QMutexLocker locker(queue_.lock());
+ while (!queue_.isEmpty()) {
+ const ViewerPlaybackFrame& pf = queue_.first();
+
+ if (pf.timestamp == time) {
+ // Frame was in queue, no need to decode anything
+ display_widget_->SetImage(pf.frame);
+ return;
+ } else {
+ queue_.removeFirst();
+ }
+ }
+}
+
OLIVE_NAMESPACE_EXIT
diff --git a/app/widget/viewer/viewerwindow.h b/app/widget/viewer/viewerwindow.h
index 8baa30c9c..2f7742c9f 100644
--- a/app/widget/viewer/viewerwindow.h
+++ b/app/widget/viewer/viewerwindow.h
@@ -24,6 +24,8 @@
#include
#include "viewerdisplay.h"
+#include "viewerplaybacktimer.h"
+#include "viewerqueue.h"
OLIVE_NAMESPACE_ENTER
@@ -32,20 +34,37 @@ class ViewerWindow : public QWidget
public:
ViewerWindow(QWidget* parent = nullptr);
- ViewerDisplayWidget* gl_widget() const;
+ ViewerDisplayWidget* display_widget() const;
/**
* @brief Used to adjust resulting picture to be the right aspect ratio
*/
void SetResolution(int width, int height);
+ ViewerQueue* queue() {
+ return &queue_;
+ }
+
+ void Play(const int64_t &start_timestamp, const int &playback_speed, const rational &timebase);
+
+ void Pause();
+
protected:
virtual void keyPressEvent(QKeyEvent* e) override;
virtual void closeEvent(QCloseEvent* e) override;
+private slots:
+ void UpdateFromQueue();
+
private:
- ViewerDisplayWidget* gl_widget_;
+ ViewerDisplayWidget* display_widget_;
+
+ ViewerQueue queue_;
+
+ ViewerPlaybackTimer timer_;
+
+ rational playback_timebase_;
};
diff --git a/appveyor.yml b/appveyor.yml
index ed053f1e1..436930972 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -14,9 +14,9 @@ install:
- cd %APPVEYOR_BUILD_FOLDER%
build_script:
- cmd: .appveyor\build.bat
-artifacts:
-- path: Olive*.zip
- name: Olive Portable
-- path: Olive*.exe
- name: Olive Installer
+#artifacts:
+#- path: Olive*.zip
+# name: Olive Portable
+#- path: Olive*.exe
+# name: Olive Installer
cache: c:\tools\vcpkg\installed\