Commit 6b4b06c0 authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Add slowmotion effect in experimental qml builtin effect stack

parent d4bc6189
......@@ -51,6 +51,15 @@ AssetPanel::AssetPanel(QWidget *parent)
{
QHBoxLayout *tLayout = new QHBoxLayout;
tLayout->addWidget(m_assetTitle);
m_switchBuiltStack = new QToolButton(this);
m_switchBuiltStack->setIcon(KoIconUtils::themedIcon(QStringLiteral("adjustlevels")));
m_switchBuiltStack->setToolTip(i18n("Adjust clip"));
m_switchBuiltStack->setCheckable(true);
m_switchBuiltStack->setChecked(KdenliveSettings::showbuiltstack());
m_switchBuiltStack->setVisible(false);
connect(m_switchBuiltStack, &QToolButton::toggled, m_effectStackWidget, &EffectStackView::switchBuiltStack);
tLayout->addWidget(m_switchBuiltStack);
m_splitButton = new QToolButton(this);
m_splitButton->setIcon(KoIconUtils::themedIcon(QStringLiteral("view-split-left-right")));
m_splitButton->setToolTip(i18n("Compare effect"));
......@@ -62,6 +71,7 @@ AssetPanel::AssetPanel(QWidget *parent)
m_lay->addWidget(m_transitionWidget);
m_lay->addWidget(m_effectStackWidget);
m_transitionWidget->setVisible(false);
m_effectStackWidget->setVisible(false);
updatePalette();
connect(m_effectStackWidget, &EffectStackView::seekToPos, this, &AssetPanel::seekToPos);
}
......@@ -86,6 +96,7 @@ void AssetPanel::showEffectStack(const QString &clipName, std::shared_ptr<Effect
}
m_assetTitle->setText(i18n("%1 effects", clipName));
m_splitButton->setVisible(true);
m_switchBuiltStack->setVisible(true);
m_effectStackWidget->setVisible(true);
m_effectStackWidget->setModel(effectsModel, range);
}
......@@ -111,6 +122,7 @@ void AssetPanel::clear()
m_transitionWidget->unsetModel();
m_effectStackWidget->setVisible(false);
m_splitButton->setVisible(false);
m_switchBuiltStack->setVisible(false);
m_effectStackWidget->setProperty("clipId", QVariant());
m_effectStackWidget->unsetModel();
m_assetTitle->setText(QString());
......@@ -190,10 +202,23 @@ const QString AssetPanel::getStyleSheet()
void AssetPanel::processSplitEffect(bool enable)
{
ObjectType id = m_effectStackWidget->stackOwner();
ObjectType id = m_effectStackWidget->stackOwner().first;
if (id == ObjectType::TimelineClip) {
emit doSplitEffect(enable);
} else if (id == ObjectType::BinClip) {
emit doSplitBinEffect(enable);
}
}
ObjectId AssetPanel::effectStackOwner()
{
if (!m_effectStackWidget->isVisible()) {
return ObjectId(ObjectType::NoItem, -1);
}
return m_effectStackWidget->stackOwner();
}
void AssetPanel::parameterChanged(QString name, int value)
{
emit changeSpeed(value);
}
......@@ -26,6 +26,8 @@
#include <QVBoxLayout>
#include <memory>
#include "definitions.h"
class KSqueezedTextLabel;
class QToolButton;
......@@ -58,11 +60,14 @@ public:
/* @brief This method should be called when the style changes */
void updatePalette();
/* @brief Returns the object type / id of effectstack owner */
ObjectId effectStackOwner();
public slots:
/** @brief Clear panel if displaying itemId */
void clearAssetPanel(int itemId);
void adjustAssetPanelRange(int itemId, int in, int out);
void parameterChanged(QString name, int value);
protected:
/** @brief Return the stylesheet used to display the panel (based on current palette). */
......@@ -73,6 +78,7 @@ protected:
EffectStackView *m_effectStackWidget;
private:
QToolButton *m_switchBuiltStack;
QToolButton *m_splitButton;
private slots:
......@@ -82,6 +88,7 @@ signals:
void doSplitEffect(bool);
void doSplitBinEffect(bool);
void seekToPos(int);
void changeSpeed(int);
};
#endif
......@@ -335,6 +335,10 @@ bool ProjectClip::setProducer(std::shared_ptr<Mlt::Producer> producer, bool repl
m_temporaryUrl.clear();
if (m_clipType == Audio) {
m_thumbnail = KoIconUtils::themedIcon(QStringLiteral("audio-x-generic"));
} else if (m_clipType == Image) {
if (getProducerIntProperty(QStringLiteral("meta.media.width")) < 8 || getProducerIntProperty(QStringLiteral("meta.media.height")) < 8) {
KMessageBox::information(QApplication::activeWindow(), i18n("Image dimension smaller than 8 pixels.\nThis is not correctly supported by our video framework."));
}
}
m_duration = getStringDuration();
m_clipStatus = StatusReady;
......
......@@ -455,7 +455,8 @@ void Core::displayMessage(const QString &message, MessageType type, int timeout)
void Core::clearAssetPanel(int itemId)
{
m_mainWindow->clearAssetPanel(itemId);
if (m_guiConstructed)
m_mainWindow->clearAssetPanel(itemId);
}
void Core::adjustAssetRange(int itemId, int in, int out)
......@@ -526,3 +527,8 @@ void Core::invalidateItem(ObjectId itemId)
break;
}
}
double Core::getClipSpeed(int id) const
{
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipSpeed(id);
}
......@@ -152,6 +152,7 @@ public:
int getCompositionATrack(int cid) const;
void setCompositionATrack(int cid, int aTrack);
std::shared_ptr<DocUndoStack> undoStack();
double getClipSpeed(int id) const;
void invalidateItem(ObjectId itemId);
void prepareShutdown();
......
......@@ -12,5 +12,6 @@ set(kdenlive_SRCS
effects/effectstack/view/abstractcollapsiblewidget.cpp
effects/effectstack/view/collapsibleeffectview.cpp
effects/effectstack/view/effectstackview.cpp
effects/effectstack/view/builtstack.cpp
PARENT_SCOPE)
......@@ -20,9 +20,12 @@
***************************************************************************/
#include "effectstackview.hpp"
#include "builtstack.hpp"
#include "assets/assetlist/view/qmltypes/asseticonprovider.hpp"
#include "assets/view/assetparameterview.hpp"
#include "assets/assetpanel.hpp"
#include "collapsibleeffectview.hpp"
#include "kdenlivesettings.h"
#include "core.h"
#include "effects/effectstack/model/effectitemmodel.hpp"
#include "effects/effectstack/model/effectstackmodel.hpp"
......@@ -66,7 +69,7 @@ void WidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
}
EffectStackView::EffectStackView(QWidget *parent)
EffectStackView::EffectStackView(AssetPanel *parent)
: QWidget(parent)
, m_thumbnailer(new AssetIconProvider(true))
, m_range(-1, -1)
......@@ -78,6 +81,10 @@ EffectStackView::EffectStackView(QWidget *parent)
m_lay->setSpacing(0);
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
setAcceptDrops(true);
m_builtStack = new BuiltStack(parent);
m_builtStack->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
m_lay->addWidget(m_builtStack);
m_builtStack->setVisible(KdenliveSettings::showbuiltstack());
m_effectsTree = new QTreeView(this);
m_effectsTree->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
m_effectsTree->setHeaderHidden(true);
......@@ -86,6 +93,7 @@ EffectStackView::EffectStackView(QWidget *parent)
// m_effectsTree->viewport()->setAutoFillBackground(false);
m_effectsTree->setStyleSheet(style);
m_lay->addWidget(m_effectsTree);
m_lay->setStretch(1, 10);
}
EffectStackView::~EffectStackView()
......@@ -158,6 +166,7 @@ void EffectStackView::setModel(std::shared_ptr<EffectStackModel> model, QPair<in
qDebug()<<"MUTEX UNLOCK!!!!!!!!!!!! setmodel";
loadEffects(range);
connect(m_model.get(), &EffectStackModel::dataChanged, this, &EffectStackView::refresh);
m_builtStack->setModel(model, stackOwner());
}
void EffectStackView::loadEffects(QPair<int, int> range, int start, int end)
......@@ -275,10 +284,16 @@ void EffectStackView::setRange(int in, int out)
}
}
ObjectType EffectStackView::stackOwner() const
ObjectId EffectStackView::stackOwner() const
{
if (m_model) {
return m_model->getOwnerId().first;
return m_model->getOwnerId();
}
return ObjectType::NoItem;
return ObjectId(ObjectType::NoItem, -1);
}
void EffectStackView::switchBuiltStack(bool show)
{
m_builtStack->setVisible(show);
KdenliveSettings::setShowbuiltstack(show);
}
......@@ -35,6 +35,8 @@ class AssetParameterModel;
class EffectStackModel;
class EffectItemModel;
class AssetIconProvider;
class BuiltStack;
class AssetPanel;
class WidgetDelegate : public QStyledItemDelegate
{
......@@ -54,12 +56,12 @@ class EffectStackView : public QWidget
Q_OBJECT
public:
EffectStackView(QWidget *parent = nullptr);
EffectStackView(AssetPanel *parent);
virtual ~EffectStackView();
void setModel(std::shared_ptr<EffectStackModel> model, QPair<int, int> range);
void unsetModel(bool reset = true);
void setRange(int in, int out);
ObjectType stackOwner() const;
ObjectId stackOwner() const;
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
......@@ -68,6 +70,7 @@ protected:
private:
QMutex m_mutex;
QVBoxLayout *m_lay;
BuiltStack *m_builtStack;
QTreeView *m_effectsTree;
std::shared_ptr<EffectStackModel> m_model;
std::vector<CollapsibleEffectView *> m_widgets;
......@@ -82,6 +85,9 @@ private slots:
void slotStartDrag(QPixmap pix, std::shared_ptr<EffectItemModel> effectModel);
void slotActivateEffect(std::shared_ptr<EffectItemModel> effectModel);
public slots:
void switchBuiltStack(bool show);
signals:
void doActivateEffect(QModelIndex);
void seekToPos(int);
......
import QtQuick 2.6
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
Rectangle {
id: root
objectName: "builtinstack"
signal valueChanged(string text, int val)
SystemPalette { id: activePalette }
color: activePalette.window
signal clipClicked()
FontMetrics {
id: fontMetrics
font.family: "Arial"
}
EffectSlider {
id: slider1
sliderIcon: 'speedometer'
sliderLabel: 'Speed'
paramName: 'speed'
slider_max: 300
slider_def: 100
}
function resetStack() {
//slider1.resetSlider()
}
function setSpeed(speed) {
if (speed == 0) {
speed = 100;
}
slider1.setValue(speed)
}
}
import QtQuick 2.6
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Layouts 1.3
Item {
id: sliderroot
property string sliderIcon
property string sliderLabel
property string paramName
property int slider_max: 100
property int slider_min: 0
property int slider_def: 100
property bool blockSignals: false
height: 20
anchors.left: parent.left
anchors.right: parent.right
function resetSlider() {
slider.value = sliderroot.slider_def
}
function setValue(val) {
sliderroot.blockSignals = true
slider.value = val
sliderroot.blockSignals = false
}
RowLayout{
spacing: 0
anchors.fill: parent
anchors.margins: 0
ToolButton {
id: enableButton
implicitHeight: 20
implicitWidth: 20
iconName: sliderroot.sliderIcon
visible: sliderroot.sliderIcon != ''
checkable: true
checked: true
Layout.rightMargin: 2
}
Slider {
id: slider
Layout.fillWidth: true
Layout.minimumWidth: 50
Layout.maximumWidth: 500
//TODO: don't hardcode
height: sliderroot.sliderLabel == '' ? 12 : 20
tickmarksEnabled: false
maximumValue: sliderroot.slider_max
minimumValue: sliderroot.slider_min
value: sliderroot.slider_def
stepSize: 1
enabled: enableButton.checked
opacity: enabled ? 1 : 0.4
updateValueWhileDragging: true
onValueChanged: {
spinbox.value = value
if (!sliderroot.blockSignals) {
root.valueChanged(sliderroot.paramName, value)
}
}
/*Text {
text: sliderroot.sliderLabel
leftPadding: 5
opacity: 0.5
color: activePalette.text
}*/
style: SliderStyle {
handle: Rectangle {
height: slider.height
width: 4
radius: 2
color: container.containsMouse ? activePalette.highlight : activePalette.text
}
groove: Rectangle {
anchors.bottom: parent.bottom
implicitHeight: slider.height / 4
implicitWidth: 100
radius: 4
border.color: activePalette.dark
color: activePalette.base
Rectangle {
height: parent.height
width: styleData.handlePosition + 2
implicitHeight: slider.height
implicitWidth: 100
radius: 4
color: activePalette.highlight
opacity: container.containsMouse ? 0.6 : 0.3
}
}
}
Repeater {
id: repeater
model: 9
Rectangle {
color: activePalette.text
width: 1 ; height: (index % 2 == 0) ? 5 : 3
y: slider.height - height
x: (index * slider.width) / (repeater.count-1)
opacity: 0.5
}
}
Component.onCompleted: {
// Create some controls.
//slider.onValueChanged.connect(appWindow.sliderChanged)
//spinBox.onValueChanged.connect(appWindow.spinBoxChanged)
}
}
SpinBox {
id: spinbox
maximumValue: sliderroot.slider_max
minimumValue: sliderroot.slider_min
value: sliderroot.slider_def
enabled: enableButton.checked
onValueChanged: {
slider.value = value
}
style: SpinBoxStyle{
textColor: activePalette.highlight
background: Rectangle {
implicitWidth: 60
implicitHeight: 20
//control.hoverEnabled: true
border.color: control.focus ? "red" : control.hovered ? activePalette.highlight : "transparent"
color: 'transparent'
radius: 2
}
}
}
}
MouseArea {
id: container
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
enabled: enableButton.checked
}
}
......@@ -728,7 +728,10 @@
<label>When editing an effect with position, seek to the keyframe pos.</label>
<default>false</default>
</entry>
<entry name="showbuiltstack" type="Bool">
<label>Show builtin effect stack.</label>
<default>false</default>
</entry>
<entry name="showeffectinfo" type="Bool">
<label>Show small effect description in effects list.</label>
<default>false</default>
......
......@@ -305,6 +305,7 @@ void MainWindow::init()
connect(m_assetPanel, &AssetPanel::doSplitEffect, m_projectMonitor, &Monitor::slotSwitchCompare);
connect(m_assetPanel, &AssetPanel::doSplitBinEffect, m_clipMonitor, &Monitor::slotSwitchCompare);
connect(m_assetPanel, &AssetPanel::changeSpeed, this, &MainWindow::slotChangeSpeed);
connect(m_timelineTabs, &TimelineTabs::showTransitionModel, m_assetPanel, &AssetPanel::showTransition);
connect(m_timelineTabs, &TimelineTabs::showClipEffectStack, m_assetPanel, &AssetPanel::showEffectStack);
connect(pCore->bin(), &Bin::requestShowEffectStack, m_assetPanel, &AssetPanel::showEffectStack);
......@@ -4138,6 +4139,15 @@ TimelineWidget *MainWindow::getCurrentTimeline() const
return m_timelineTabs->getCurrentTimeline();
}
void MainWindow::slotChangeSpeed(int speed)
{
ObjectId owner = m_assetPanel->effectStackOwner();
//TODO: manage bin clips / tracks
if (owner.first == ObjectType::TimelineClip) {
getCurrentTimeline()->controller()->changeItemSpeed(owner.second, speed);
}
}
#ifdef DEBUG_MAINW
#undef DEBUG_MAINW
#endif
......@@ -465,6 +465,7 @@ private slots:
void setTrimMode(const QString &mode);
/** @brief Set timeline toolbar icon size. */
void setTimelineToolbarIconSize(QAction *a);
void slotChangeSpeed(int speed);
signals:
Q_SCRIPTABLE void abortRenderJob(const QString &url);
......
......@@ -171,6 +171,15 @@ int ClipModel::getIntProperty(const QString &name) const
return service()->get_int(name.toUtf8().constData());
}
double ClipModel::getDoubleProperty(const QString &name) const
{
READ_LOCK();
if (service()->parent().is_valid()) {
return service()->parent().get_double(name.toUtf8().constData());
}
return service()->get_double(name.toUtf8().constData());
}
Mlt::Producer *ClipModel::service() const
{
READ_LOCK();
......@@ -233,6 +242,19 @@ bool ClipModel::isAudioOnly() const
void ClipModel::refreshProducerFromBin()
{
if (getProperty("mlt_service") == QLatin1String("timewarp")) {
// slowmotion producer, keep it
int space = -1;
if (m_currentTrackId != -1) {
if (auto ptr = m_parent.lock()) {
space = ptr->getTrackById(m_currentTrackId)->getBlankSizeNearClip(m_id, true);
} else {
qDebug() << "Error : Moving clip failed because parent timeline is not available anymore";
Q_ASSERT(false);
}
}
return useTimewarpProducer(m_producer->get_double("warp_speed"), space);
}
QWriteLocker locker(&m_lock);
int in = getIn();
int out = getOut();
......@@ -246,6 +268,47 @@ void ClipModel::refreshProducerFromBin()
m_endlessResize = !binClip->hasLimitedDuration();
}
void ClipModel::useTimewarpProducer(double speed, int extraSpace)
{
QWriteLocker locker(&m_lock);
// TODO: disable timewarp on color clips
int in = getIn();
int out = getOut();
int warp_in;
int warp_out;
double currentSpeed = 1.0;
qDebug()<<"// SLOWMO CLIP SERVICE: "<<getProperty("mlt_service");
if (getProperty("mlt_service") == QLatin1String("timewarp")) {
// slowmotion producer, get current speed
warp_in = m_producer->get_int("warp_in");
warp_out = m_producer->get_int("warp_out");
currentSpeed = m_producer->get_double("warp_speed");
} else {
// store original in/out
warp_in = in;
warp_out = out;
}
qDebug()<<"++++\n//// USING TIMEWARP: "<<warp_in<<"-"<<warp_out;
in = warp_in / speed;
out = warp_out / speed;
std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
std::shared_ptr<Mlt::Producer> originalProducer = binClip->originalProducer();
if (speed == 1.0) {
m_producer.reset(originalProducer->cut(in, out));
} else {
QString resource = QString("%1:%2").arg(speed).arg(originalProducer->get("resource"));
std::shared_ptr<Mlt::Producer> warpProducer(new Mlt::Producer(*originalProducer->profile(), "timewarp", resource.toUtf8().constData()));
m_producer.reset(warpProducer->cut(in, out));
}
// replant effect stack in updated service
m_effectStack->resetService(m_producer);
m_producer->set("kdenlive:id", binClip->AbstractProjectItem::clipId().toUtf8().constData());
m_producer->set("_kdenlive_cid", m_id);
m_producer->set("warp_in", warp_in);
m_producer->set("warp_out", warp_out);
m_endlessResize = !binClip->hasLimitedDuration();
}
QVariant ClipModel::getAudioWaveform()
{
READ_LOCK();
......@@ -277,3 +340,11 @@ int ClipModel::fadeOut() const
return m_effectStack->getFadePosition(false);
}
double ClipModel::getSpeed() const
{
if (getProperty("mlt_service") == QLatin1String("timewarp")) {
// slowmotion producer, get current speed
return m_producer->parent().get_double("warp_speed");
}
return 1.0;
}
......@@ -71,6 +71,7 @@ public:
*/
const QString getProperty(const QString &name) const override;
int getIntProperty(const QString &name) const;
double getDoubleProperty(const QString &name) const;
/* @brief returns the length of the item on the timeline
*/
......@@ -119,12 +120,15 @@ protected:
/* @brief This functions should be called when the producer of the binClip changes, to allow refresh */
void refreshProducerFromBin();
/* @brief This functions replaces the current producer with a slowmotion one */
void useTimewarpProducer(double speed, int extraSpace);
/** @brief Returns the marker model associated with this clip */
std::shared_ptr<MarkerListModel> getMarkerModel() const;
bool hasAudio() const;
bool isAudioOnly() const;
double getSpeed() const;
protected:
std::shared_ptr<Mlt::