diff --git a/data/effects/CMakeLists.txt b/data/effects/CMakeLists.txt index b90cc4180..764e5c6b2 100644 --- a/data/effects/CMakeLists.txt +++ b/data/effects/CMakeLists.txt @@ -1,138 +1,138 @@ INSTALL (FILES audiowave.xml audiowaveform.xml automask.xml audiobalance.xml audiopan.xml boxblur.xml brightness.xml channelcopy.xml charcoal.xml chroma_hold.xml chroma.xml crop.xml dust.xml dynamictext.xml freeze.xml gamma.xml grain.xml greyscale.xml invert.xml loudness.xml luma.xml mirror.xml mute.xml normalise.xml oldfilm.xml pan_zoom.xml obscure.xml region.xml rotation.xml rotation_keyframable.xml scratchlines.xml sepia.xml sox_bass.xml sox_gain.xml sox_phaser.xml sox_band.xml sox_echo.xml sox_flanger.xml sox_stretch.xml threshold.xml volume.xml wave.xml fadein.xml fadeout.xml frei0r_alpha0ps.xml frei0r_alphagrad.xml frei0r_alphaspot.xml frei0r_balanc0r.xml frei0r_baltan.xml frei0r_bezier_curves.xml frei0r_brightness.xml frei0r_cartoon.xml frei0r_cluster.xml frei0r_colgate.xml frei0r_coloradj_rgb.xml frei0r_colordistance.xml frei0r_colortap.xml frei0r_contrast0r.xml frei0r_c0rners.xml frei0r_curves.xml frei0r_d90stairsteppingfix.xml frei0r_defish0r.xml frei0r_delay0r.xml frei0r_delaygrab.xml frei0r_distort0r.xml frei0r_edgeglow.xml frei0r_equaliz0r.xml frei0r_flippo.xml frei0r_glow.xml frei0r_hqdn3d.xml frei0r_hueshift0r.xml frei0r_iirblur.xml frei0r_keyspillm0pup.xml frei0r_lenscorrection.xml frei0r_letterb0xed.xml frei0r_levels.xml frei0r_lightgraffiti.xml frei0r_luminance.xml frei0r_mask0mate.xml frei0r_medians.xml frei0r_nervous.xml frei0r_nosync0r.xml frei0r_pixeliz0r.xml frei0r_pr0be.xml frei0r_pr0file.xml frei0r_primaries.xml frei0r_rgbparade.xml frei0r_saturat0r.xml frei0r_scale0tilt.xml frei0r_scanline0r.xml frei0r_select0r.xml frei0r_sharpness.xml frei0r_sobel.xml frei0r_sopsat.xml frei0r_squareblur.xml frei0r_tehroxx0r.xml frei0r_three_point_balance.xml frei0r_threelay0r.xml frei0r_threshold0r.xml frei0r_timeout.xml frei0r_tint0r.xml frei0r_twolay0r.xml frei0r_vectorscope.xml frei0r_vertigo.xml frei0r_vignette.xml frei0r_facebl0r.xml frei0r_facedetect.xml fade_from_black.xml fade_to_black.xml gain.xml lift_gamma_gain.xml movit_blur.xml movit_deconvolution_sharpen.xml movit_diffusion.xml movit_glow.xml movit_lift_gamma_gain.xml movit_mirror.xml movit_opacity.xml movit_rect.xml movit_saturation.xml movit_unsharp_mask.xml movit_vignette.xml movit_white_balance.xml qtblend.xml rotoscoping.xml speed.xml swapchannels.xml tcolor.xml -vignette.xml tracker.xml - +vignette.xml +vidstab.xml DESTINATION ${DATA_INSTALL_DIR}/kdenlive/effects) add_subdirectory(update) add_subdirectory(avfilter) diff --git a/data/effects/vidstab.xml b/data/effects/vidstab.xml new file mode 100644 index 000000000..823de46be --- /dev/null +++ b/data/effects/vidstab.xml @@ -0,0 +1,64 @@ + + + Stabilize + Adjust audio volume with keyframes + Dan Dennedy + + Accuracy + Accuracy of Shakiness detection + + + Shakiness + How shaky is the Video + + + Stepsize + Stepsize of Detection process minimum around + + + Min. contrast + Below this Contrast Field is discarded + + + Smoothing + Number of frames for lowpass filtering + + + Max shift + Max number of pixels to shift (-1 = no limit) + + + Max angle + Max angle to rotate (in rad) + + + Crop + Disabled = keep border, enabled = black background + + + Zoom + Additional zoom during transform + + + Optimal Zoom + Automatically determine optimal zoom. 1 - static zoom, 2 - adaptive zoom + + + Optimal Zoom Speed + Zoom per frame (used when optimal zoom = 2) + + + Sharpen + Sharpen transformed image + + + Show fields + 0 = draw nothing 1 or 2 = show fields and transforms + + + Tripod + Reference frame + + + + diff --git a/src/assets/assetpanel.cpp b/src/assets/assetpanel.cpp index adda466de..0e6ee0b86 100644 --- a/src/assets/assetpanel.cpp +++ b/src/assets/assetpanel.cpp @@ -1,352 +1,352 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * 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 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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 "assetpanel.hpp" #include "core.h" #include "definitions.h" #include "effects/effectstack/model/effectitemmodel.hpp" #include "effects/effectstack/model/effectstackmodel.hpp" #include "effects/effectstack/view/effectstackview.hpp" #include "kdenlivesettings.h" #include "model/assetparametermodel.hpp" #include "transitions/transitionsrepository.hpp" #include "transitions/view/transitionstackview.hpp" #include "view/assetparameterview.hpp" #include #include #include #include #include #include #include #include #include #include AssetPanel::AssetPanel(QWidget *parent) : QWidget(parent) , m_lay(new QVBoxLayout(this)) , m_assetTitle(new KSqueezedTextLabel(this)) , m_container(new QWidget(this)) , m_transitionWidget(new TransitionStackView(this)) , m_effectStackWidget(new EffectStackView(this)) { auto *buttonToolbar = new QToolBar(this); buttonToolbar->addWidget(m_assetTitle); int size = style()->pixelMetric(QStyle::PM_SmallIconSize); QSize iconSize(size, size); buttonToolbar->setIconSize(iconSize); // spacer QWidget *empty = new QWidget(); empty->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); buttonToolbar->addWidget(empty); m_switchBuiltStack = new QToolButton(this); m_switchBuiltStack->setIcon(QIcon::fromTheme(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); buttonToolbar->addWidget(m_switchBuiltStack); m_splitButton = new KDualAction(i18n("Normal view"), i18n("Compare effect"), this); m_splitButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("view-right-close"))); m_splitButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); m_splitButton->setToolTip(i18n("Compare effect")); m_splitButton->setVisible(false); connect(m_splitButton, &KDualAction::activeChangedByUser, this, &AssetPanel::processSplitEffect); buttonToolbar->addAction(m_splitButton); m_enableStackButton = new KDualAction(i18n("Effects disabled"), i18n("Effects enabled"), this); m_enableStackButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("hint"))); m_enableStackButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("visibility"))); connect(m_enableStackButton, &KDualAction::activeChangedByUser, this, &AssetPanel::enableStack); m_enableStackButton->setVisible(false); buttonToolbar->addAction(m_enableStackButton); m_timelineButton = new KDualAction(i18n("Hide keyframes"), i18n("Display keyframes in timeline"), this); m_timelineButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("adjustlevels"))); m_timelineButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("adjustlevels"))); m_timelineButton->setToolTip(i18n("Display keyframes in timeline")); m_timelineButton->setVisible(false); connect(m_timelineButton, &KDualAction::activeChangedByUser, this, &AssetPanel::showKeyframes); buttonToolbar->addAction(m_timelineButton); m_lay->addWidget(buttonToolbar); m_lay->setContentsMargins(0, 0, 0, 0); m_lay->setSpacing(0); auto *lay = new QVBoxLayout(m_container); lay->setContentsMargins(0, 0, 0, 0); lay->addWidget(m_transitionWidget); lay->addWidget(m_effectStackWidget); auto *sc = new QScrollArea; sc->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); sc->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); sc->setFrameStyle(QFrame::NoFrame); sc->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding)); m_container->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding)); sc->setWidgetResizable(true); m_lay->addWidget(sc); sc->setWidget(m_container); m_transitionWidget->setVisible(false); m_effectStackWidget->setVisible(false); updatePalette(); connect(m_effectStackWidget, &EffectStackView::seekToPos, this, &AssetPanel::seekToPos); connect(m_effectStackWidget, &EffectStackView::reloadEffect, this, &AssetPanel::reloadEffect); connect(m_transitionWidget, &TransitionStackView::seekToTransPos, this, &AssetPanel::seekToPos); connect(m_effectStackWidget, &EffectStackView::updateEnabledState, [this]() { m_enableStackButton->setActive(m_effectStackWidget->isStackEnabled()); }); } void AssetPanel::showTransition(int tid, const std::shared_ptr &transitionModel) { Q_UNUSED(tid) ObjectId id = transitionModel->getOwnerId(); if (m_transitionWidget->stackOwner() == id) { // already on this effect stack, do nothing return; } clear(); QString transitionId = transitionModel->getAssetId(); QString transitionName = TransitionsRepository::get()->getName(transitionId); - m_assetTitle->setText(i18n("%1 properties").arg(i18n(transitionName.toUtf8().data()))); + m_assetTitle->setText(i18n("%1 properties", i18n(transitionName.toUtf8().data()))); m_transitionWidget->setVisible(true); m_timelineButton->setVisible(true); m_enableStackButton->setVisible(false); m_transitionWidget->setModel(transitionModel, QSize(), true); } void AssetPanel::showEffectStack(const QString &itemName, const std::shared_ptr &effectsModel, QSize frameSize, bool showKeyframes) { m_splitButton->setActive(false); if (effectsModel == nullptr) { // Item is not ready m_splitButton->setVisible(false); m_enableStackButton->setVisible(false); clear(); return; } ObjectId id = effectsModel->getOwnerId(); if (m_effectStackWidget->stackOwner() == id) { // already on this effect stack, do nothing return; } clear(); QString title; bool showSplit = false; bool enableKeyframes = false; switch (id.first) { case ObjectType::TimelineClip: title = i18n("%1 effects", itemName); showSplit = true; enableKeyframes = true; break; case ObjectType::TimelineComposition: title = i18n("%1 parameters", itemName); enableKeyframes = true; break; case ObjectType::TimelineTrack: title = i18n("Track %1 effects", itemName); // TODO: track keyframes // enableKeyframes = true; break; case ObjectType::BinClip: title = i18n("Bin %1 effects", itemName); showSplit = true; break; default: title = itemName; break; } m_assetTitle->setText(title); m_splitButton->setVisible(showSplit); m_enableStackButton->setVisible(id.first != ObjectType::TimelineComposition); m_enableStackButton->setActive(effectsModel->isStackEnabled()); if (showSplit) { m_splitButton->setEnabled(effectsModel->rowCount() > 0); QObject::connect(effectsModel.get(), &EffectStackModel::dataChanged, [&]() { if (m_effectStackWidget->isEmpty()) { m_splitButton->setActive(false); } m_splitButton->setEnabled(!m_effectStackWidget->isEmpty()); }); } m_timelineButton->setVisible(enableKeyframes); m_timelineButton->setActive(showKeyframes); // Disable built stack until properly implemented // m_switchBuiltStack->setVisible(true); m_effectStackWidget->setVisible(true); m_effectStackWidget->setModel(effectsModel, frameSize); } void AssetPanel::clearAssetPanel(int itemId) { ObjectId id = m_effectStackWidget->stackOwner(); if (id.first == ObjectType::TimelineClip && id.second == itemId) { clear(); } else { id = m_transitionWidget->stackOwner(); if (id.first == ObjectType::TimelineComposition && id.second == itemId) { clear(); } } } void AssetPanel::clear() { m_transitionWidget->setVisible(false); m_transitionWidget->unsetModel(); m_effectStackWidget->setVisible(false); m_splitButton->setVisible(false); m_timelineButton->setVisible(false); m_switchBuiltStack->setVisible(false); m_effectStackWidget->unsetModel(); m_assetTitle->setText(QString()); } void AssetPanel::updatePalette() { QString styleSheet = getStyleSheet(); setStyleSheet(styleSheet); m_transitionWidget->setStyleSheet(styleSheet); m_effectStackWidget->setStyleSheet(styleSheet); } // static const QString AssetPanel::getStyleSheet() { KColorScheme scheme(QApplication::palette().currentColorGroup(), KColorScheme::View); QColor selected_bg = scheme.decoration(KColorScheme::FocusColor).color(); QColor hgh = KColorUtils::mix(QApplication::palette().window().color(), selected_bg, 0.2); QColor hover_bg = scheme.decoration(KColorScheme::HoverColor).color(); QColor light_bg = scheme.shade(KColorScheme::LightShade); QColor alt_bg = scheme.background(KColorScheme::NormalBackground).color(); QString stylesheet; // effect background stylesheet.append(QStringLiteral("QFrame#decoframe {border-bottom:2px solid " "palette(mid);background: transparent} QFrame#decoframe[active=\"true\"] {background: %1;}") .arg(hgh.name())); // effect in group background stylesheet.append( QStringLiteral("QFrame#decoframesub {border-top:1px solid palette(light);} QFrame#decoframesub[active=\"true\"] {background: %1;}").arg(hgh.name())); // group background stylesheet.append(QStringLiteral("QFrame#decoframegroup {border:2px solid palette(dark);margin:0px;margin-top:2px;} ")); // effect title bar stylesheet.append(QStringLiteral("QFrame#frame {margin-bottom:2px;} QFrame#frame[target=\"true\"] " "{background: palette(highlight);}")); // group effect title bar stylesheet.append(QStringLiteral("QFrame#framegroup {background: palette(dark);} " "QFrame#framegroup[target=\"true\"] {background: palette(highlight);} ")); // draggable effect bar content stylesheet.append(QStringLiteral("QProgressBar::chunk:horizontal {background: palette(button);border-top-left-radius: 4px;border-bottom-left-radius: 4px;} " "QProgressBar::chunk:horizontal#dragOnly {background: %1;border-top-left-radius: 4px;border-bottom-left-radius: 4px;} " "QProgressBar::chunk:horizontal:hover {background: %2;}") .arg(alt_bg.name(), selected_bg.name())); // draggable effect bar stylesheet.append(QStringLiteral("QProgressBar:horizontal {border: 1px solid palette(dark);border-top-left-radius: 4px;border-bottom-left-radius: " "4px;border-right:0px;background:%3;padding: 0px;text-align:left center} QProgressBar:horizontal:disabled {border: 1px " "solid palette(button)} QProgressBar:horizontal#dragOnly {background: %3} QProgressBar:horizontal[inTimeline=\"true\"] { " "border: 1px solid %1;border-right: 0px;background: %2;padding: 0px;text-align:left center } " "QProgressBar::chunk:horizontal[inTimeline=\"true\"] {background: %1;}") .arg(hover_bg.name(), light_bg.name(), alt_bg.name())); // spin box for draggable widget stylesheet.append( QStringLiteral("QAbstractSpinBox#dragBox {border: 1px solid palette(dark);border-top-right-radius: 4px;border-bottom-right-radius: " "4px;padding-right:0px;} QAbstractSpinBox::down-button#dragBox {width:0px;padding:0px;} QAbstractSpinBox:disabled#dragBox {border: 1px " "solid palette(button);} QAbstractSpinBox::up-button#dragBox {width:0px;padding:0px;} QAbstractSpinBox[inTimeline=\"true\"]#dragBox { " "border: 1px solid %1;} QAbstractSpinBox:hover#dragBox {border: 1px solid %2;} ") .arg(hover_bg.name(), selected_bg.name())); // group editable labels stylesheet.append(QStringLiteral("MyEditableLabel { background-color: transparent; color: palette(bright-text); border-radius: 2px;border: 1px solid " "transparent;} MyEditableLabel:hover {border: 1px solid palette(highlight);} ")); // transparent qcombobox stylesheet.append(QStringLiteral("QComboBox { background-color: transparent;} ")); return stylesheet; } void AssetPanel::processSplitEffect(bool enable) { ObjectType id = m_effectStackWidget->stackOwner().first; if (id == ObjectType::TimelineClip) { emit doSplitEffect(enable); } else if (id == ObjectType::BinClip) { emit doSplitBinEffect(enable); } } void AssetPanel::showKeyframes(bool enable) { if (m_transitionWidget->isVisible()) { pCore->showClipKeyframes(m_transitionWidget->stackOwner(), enable); } else { pCore->showClipKeyframes(m_effectStackWidget->stackOwner(), enable); } } ObjectId AssetPanel::effectStackOwner() { if (m_transitionWidget->isVisible()) { return m_transitionWidget->stackOwner(); } if (!m_effectStackWidget->isVisible()) { return ObjectId(ObjectType::NoItem, -1); } return m_effectStackWidget->stackOwner(); } bool AssetPanel::addEffect(const QString &effectId) { if (!m_effectStackWidget->isVisible()) { return false; } return m_effectStackWidget->addEffect(effectId); } void AssetPanel::enableStack(bool enable) { if (!m_effectStackWidget->isVisible()) { return; } m_effectStackWidget->enableStack(enable); } void AssetPanel::deleteCurrentEffect() { if (m_effectStackWidget->isVisible()) { m_effectStackWidget->removeCurrentEffect(); } } diff --git a/src/assets/model/assetcommand.cpp b/src/assets/model/assetcommand.cpp index 3511340bd..4317d8c7d 100644 --- a/src/assets/model/assetcommand.cpp +++ b/src/assets/model/assetcommand.cpp @@ -1,212 +1,220 @@ /*************************************************************************** * Copyright (C) 2017 by by Jean-Baptiste Mardelle * * This file is part of Kdenlive. See www.kdenlive.org. * * * * 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 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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 "assetcommand.hpp" #include "assets/keyframes/model/keyframemodellist.hpp" #include "effects/effectsrepository.hpp" #include "transitions/transitionsrepository.hpp" #include #include AssetCommand::AssetCommand(const std::shared_ptr &model, const QModelIndex &index, QString value, QUndoCommand *parent) : QUndoCommand(parent) , m_model(model) , m_index(index) , m_value(std::move(value)) , m_updateView(false) , m_stamp(QTime::currentTime()) { QLocale locale; m_name = m_model->data(index, AssetParameterModel::NameRole).toString(); const QString id = model->getAssetId(); if (EffectsRepository::get()->exists(id)) { - setText(i18n("Edit %1").arg(i18n(EffectsRepository::get()->getName(id).toUtf8().data()))); + QString effectName = EffectsRepository::get()->getName(id); + setText(i18n("Edit %1", i18n(effectName.toUtf8().data()))); } else if (TransitionsRepository::get()->exists(id)) { - setText(i18n("Edit %1").arg(i18n(TransitionsRepository::get()->getName(id).toUtf8().data()))); + QString compoName = TransitionsRepository::get()->getName(id); + setText(i18n("Edit %1", i18n(compoName.toUtf8().data()))); } QVariant previousVal = m_model->data(index, AssetParameterModel::ValueRole); m_oldValue = previousVal.type() == QVariant::Double ? locale.toString(previousVal.toDouble()) : previousVal.toString(); } void AssetCommand::undo() { m_model->setParameter(m_name, m_oldValue, true, m_index); } // virtual void AssetCommand::redo() { m_model->setParameter(m_name, m_value, m_updateView, m_index); m_updateView = true; } // virtual int AssetCommand::id() const { return 1; } // virtual bool AssetCommand::mergeWith(const QUndoCommand *other) { if (other->id() != id() || static_cast(other)->m_index != m_index || m_stamp.msecsTo(static_cast(other)->m_stamp) > 3000) { return false; } m_value = static_cast(other)->m_value; m_stamp = static_cast(other)->m_stamp; return true; } AssetMultiCommand::AssetMultiCommand(const std::shared_ptr &model, const QList indexes, const QStringList values, QUndoCommand *parent) : QUndoCommand(parent) , m_model(model) , m_indexes(indexes) , m_values(values) , m_updateView(false) , m_stamp(QTime::currentTime()) { QLocale locale; qDebug()<<"CREATING MULTIPLE COMMAND!!!\nVALUES: "<data(indexes.first(), AssetParameterModel::NameRole).toString(); const QString id = model->getAssetId(); if (EffectsRepository::get()->exists(id)) { - setText(i18n("Edit %1").arg(i18n(EffectsRepository::get()->getName(id).toUtf8().data()))); + QString effectName = EffectsRepository::get()->getName(id); + setText(i18n("Edit %1", i18n(effectName.toUtf8().data()))); } else if (TransitionsRepository::get()->exists(id)) { - setText(i18n("Edit %1").arg(i18n(TransitionsRepository::get()->getName(id).toUtf8().data()))); + QString compoName = TransitionsRepository::get()->getName(id); + setText(i18n("Edit %1", i18n(compoName.toUtf8().data()))); } for (QModelIndex ix : m_indexes) { QVariant previousVal = m_model->data(ix, AssetParameterModel::ValueRole); m_oldValues << (previousVal.type() == QVariant::Double ? locale.toString(previousVal.toDouble()) : previousVal.toString()); } } void AssetMultiCommand::undo() { int indx = 0; int max = m_indexes.size() - 1; for (const QModelIndex &ix : m_indexes) { m_model->setParameter(m_model->data(ix, AssetParameterModel::NameRole).toString(), m_oldValues.at(indx), indx == max, ix); indx++; } } // virtual void AssetMultiCommand::redo() { int indx = 0; int max = m_indexes.size() - 1; for (const QModelIndex &ix : m_indexes) { m_model->setParameter(m_model->data(ix, AssetParameterModel::NameRole).toString(), m_values.at(indx), m_updateView && indx == max, ix); indx++; } m_updateView = true; } // virtual int AssetMultiCommand::id() const { return 1; } // virtual bool AssetMultiCommand::mergeWith(const QUndoCommand *other) { if (other->id() != id() || static_cast(other)->m_indexes != m_indexes || m_stamp.msecsTo(static_cast(other)->m_stamp) > 3000) { return false; } m_values = static_cast(other)->m_values; m_stamp = static_cast(other)->m_stamp; return true; } AssetKeyframeCommand::AssetKeyframeCommand(const std::shared_ptr &model, const QModelIndex &index, QVariant value, GenTime pos, QUndoCommand *parent) : QUndoCommand(parent) , m_model(model) , m_index(index) , m_value(std::move(value)) , m_pos(pos) , m_updateView(false) , m_stamp(QTime::currentTime()) { const QString id = model->getAssetId(); if (EffectsRepository::get()->exists(id)) { - setText(i18n("Edit %1 keyframe", EffectsRepository::get()->getName(id))); + QString effectName = EffectsRepository::get()->getName(id); + setText(i18n("Edit %1 keyframe", i18n(effectName.toUtf8().data()))); } else if (TransitionsRepository::get()->exists(id)) { - setText(i18n("Edit %1 keyframe", TransitionsRepository::get()->getName(id))); + QString compoName = TransitionsRepository::get()->getName(id); + setText(i18n("Edit %1 keyframe", i18n(compoName.toUtf8().data()))); } m_oldValue = m_model->getKeyframeModel()->getKeyModel(m_index)->getInterpolatedValue(m_pos); } void AssetKeyframeCommand::undo() { m_model->getKeyframeModel()->getKeyModel(m_index)->directUpdateKeyframe(m_pos, m_oldValue); } // virtual void AssetKeyframeCommand::redo() { m_model->getKeyframeModel()->getKeyModel(m_index)->directUpdateKeyframe(m_pos, m_value); m_updateView = true; } // virtual int AssetKeyframeCommand::id() const { return 2; } // virtual bool AssetKeyframeCommand::mergeWith(const QUndoCommand *other) { if (other->id() != id() || static_cast(other)->m_index != m_index || m_stamp.msecsTo(static_cast(other)->m_stamp) > 1000) { return false; } m_value = static_cast(other)->m_value; m_stamp = static_cast(other)->m_stamp; return true; } AssetUpdateCommand::AssetUpdateCommand(const std::shared_ptr &model, QVector> parameters, QUndoCommand *parent) : QUndoCommand(parent) , m_model(model) , m_value(std::move(parameters)) { const QString id = model->getAssetId(); if (EffectsRepository::get()->exists(id)) { - setText(i18n("Update %1", EffectsRepository::get()->getName(id))); + QString effectName = EffectsRepository::get()->getName(id); + setText(i18n("Update %1", i18n(effectName.toUtf8().data()))); } else if (TransitionsRepository::get()->exists(id)) { - setText(i18n("Update %1", TransitionsRepository::get()->getName(id))); + QString compoName = TransitionsRepository::get()->getName(id); + setText(i18n("Update %1", i18n(compoName.toUtf8().data()))); } m_oldValue = m_model->getAllParameters(); } void AssetUpdateCommand::undo() { m_model->setParameters(m_oldValue); } // virtual void AssetUpdateCommand::redo() { m_model->setParameters(m_value); } // virtual int AssetUpdateCommand::id() const { return 3; } diff --git a/src/assets/view/assetparameterview.cpp b/src/assets/view/assetparameterview.cpp index fb41dc8c5..144a86c7f 100644 --- a/src/assets/view/assetparameterview.cpp +++ b/src/assets/view/assetparameterview.cpp @@ -1,351 +1,351 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * 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 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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 "assetparameterview.hpp" #include "assets/model/assetcommand.hpp" #include "assets/model/assetparametermodel.hpp" #include "assets/view/widgets/abstractparamwidget.hpp" #include "assets/view/widgets/keyframewidget.hpp" #include "core.h" #include #include #include #include #include #include #include #include #include AssetParameterView::AssetParameterView(QWidget *parent) : QWidget(parent) { m_lay = new QVBoxLayout(this); m_lay->setContentsMargins(0, 0, 0, 2); m_lay->setSpacing(0); setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); // Presets Combo m_presetMenu = new QMenu(this); } void AssetParameterView::setModel(const std::shared_ptr &model, QSize frameSize, bool addSpacer) { unsetModel(); QMutexLocker lock(&m_lock); m_model = model; const QString paramTag = model->getAssetId(); QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/")); const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(paramTag)); connect(this, &AssetParameterView::updatePresets, [this, presetFile](const QString &presetName) { m_presetMenu->clear(); m_presetGroup.reset(new QActionGroup(this)); m_presetGroup->setExclusive(true); m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reset Effect"), this, SLOT(resetValues())); // Save preset m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save-as-template")), i18n("Save preset"), this, SLOT(slotSavePreset())); m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save-as-template")), i18n("Update current preset"), this, SLOT(slotUpdatePreset())); m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete preset"), this, SLOT(slotDeletePreset())); m_presetMenu->addSeparator(); QStringList presets = m_model->getPresetList(presetFile); for (const QString &pName : presets) { QAction *ac = m_presetMenu->addAction(pName, this, SLOT(slotLoadPreset())); m_presetGroup->addAction(ac); ac->setData(pName); ac->setCheckable(true); if (pName == presetName) { ac->setChecked(true); } } }); emit updatePresets(); connect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh); if (paramTag.endsWith(QStringLiteral("lift_gamma_gain"))) { // Special case, the colorwheel widget manages several parameters QModelIndex index = model->index(0, 0); auto w = AbstractParamWidget::construct(model, index, frameSize, this); connect(w, &AbstractParamWidget::valuesChanged, this, &AssetParameterView::commitMultipleChanges); connect(w, &AbstractParamWidget::valueChanged, this, &AssetParameterView::commitChanges); m_lay->addWidget(w); m_widgets.push_back(w); } else { for (int i = 0; i < model->rowCount(); ++i) { QModelIndex index = model->index(i, 0); auto type = model->data(index, AssetParameterModel::TypeRole).value(); if (m_mainKeyframeWidget && (type == ParamType::Geometry || type == ParamType::Animated || type == ParamType::RestrictedAnim || type == ParamType::KeyframeParam)) { // Keyframe widget can have some extra params that shouldn't build a new widget qDebug() << "// FOUND ADDED PARAM"; m_mainKeyframeWidget->addParameter(index); } else { auto w = AbstractParamWidget::construct(model, index, frameSize, this); connect(this, &AssetParameterView::initKeyframeView, w, &AbstractParamWidget::slotInitMonitor); if (type == ParamType::KeyframeParam || type == ParamType::AnimatedRect || type == ParamType::Roto_spline) { m_mainKeyframeWidget = static_cast(w); } connect(w, &AbstractParamWidget::valueChanged, this, &AssetParameterView::commitChanges); connect(w, &AbstractParamWidget::seekToPos, this, &AssetParameterView::seekToPos); m_lay->addWidget(w); m_widgets.push_back(w); } } } if (addSpacer) { m_lay->addStretch(); } } QVector> AssetParameterView::getDefaultValues() const { QLocale locale; QVector> values; for (int i = 0; i < m_model->rowCount(); ++i) { QModelIndex index = m_model->index(i, 0); QString name = m_model->data(index, AssetParameterModel::NameRole).toString(); auto type = m_model->data(index, AssetParameterModel::TypeRole).value(); QVariant defaultValue = m_model->data(index, AssetParameterModel::DefaultRole); if (type == ParamType::KeyframeParam || type == ParamType::AnimatedRect) { QString val = type == ParamType::KeyframeParam ? locale.toString(defaultValue.toDouble()) : defaultValue.toString(); if (!val.contains(QLatin1Char('='))) { val.prepend(QStringLiteral("%1=").arg(m_model->data(index, AssetParameterModel::ParentInRole).toInt())); defaultValue = QVariant(val); } } values.append({name, defaultValue}); } return values; } void AssetParameterView::resetValues() { const QVector> values = getDefaultValues(); auto *command = new AssetUpdateCommand(m_model, values); pCore->pushUndo(command); // Unselect preset if any QAction *ac = m_presetGroup->checkedAction(); if (ac) { ac->setChecked(false);; } } void AssetParameterView::commitChanges(const QModelIndex &index, const QString &value, bool storeUndo) { // Warning: please note that some widgets (for example keyframes) do NOT send the valueChanged signal and do modifications on their own auto *command = new AssetCommand(m_model, index, value); - if (storeUndo) { + if (storeUndo && m_model->getOwnerId().second != -1) { pCore->pushUndo(command); } else { command->redo(); delete command; } } void AssetParameterView::commitMultipleChanges(const QList indexes, const QStringList &values, bool storeUndo) { // Warning: please note that some widgets (for example keyframes) do NOT send the valueChanged signal and do modifications on their own auto *command = new AssetMultiCommand(m_model, indexes, values); if (storeUndo) { pCore->pushUndo(command); } else { command->redo(); delete command; } } void AssetParameterView::unsetModel() { QMutexLocker lock(&m_lock); if (m_model) { // if a model is already there, we have to disconnect signals first disconnect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh); } m_mainKeyframeWidget = nullptr; // clear layout m_widgets.clear(); QLayoutItem *child; while ((child = m_lay->takeAt(0)) != nullptr) { if (child->layout()) { QLayoutItem *subchild; while ((subchild = child->layout()->takeAt(0)) != nullptr) { delete subchild->widget(); delete subchild->spacerItem(); } } delete child->widget(); delete child->spacerItem(); } // Release ownership of smart pointer m_model.reset(); } void AssetParameterView::refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { QMutexLocker lock(&m_lock); if (m_widgets.size() == 0) { // no visible param for this asset, abort return; } Q_UNUSED(roles); // We are expecting indexes that are children of the root index, which is "invalid" Q_ASSERT(!topLeft.parent().isValid()); // We make sure the range is valid if (m_mainKeyframeWidget) { m_mainKeyframeWidget->slotRefresh(); } else { auto type = m_model->data(m_model->index(topLeft.row(), 0), AssetParameterModel::TypeRole).value(); if (type == ParamType::ColorWheel) { // Some special widgets, like colorwheel handle multiple params so we can have cases where param index row is greater than the number of widgets. // Should be better managed m_widgets[0]->slotRefresh(); return; } int max; if (!bottomRight.isValid()) { max = (int)m_widgets.size() - 1; } else { max = bottomRight.row(); } Q_ASSERT(max < (int)m_widgets.size()); for (auto i = (size_t)topLeft.row(); i <= max; ++i) { m_widgets[i]->slotRefresh(); } } } int AssetParameterView::contentHeight() const { return m_lay->minimumSize().height(); } MonitorSceneType AssetParameterView::needsMonitorEffectScene() const { if (m_mainKeyframeWidget) { return m_mainKeyframeWidget->requiredScene(); } for (int i = 0; i < m_model->rowCount(); ++i) { QModelIndex index = m_model->index(i, 0); auto type = m_model->data(index, AssetParameterModel::TypeRole).value(); if (type == ParamType::Geometry) { return MonitorSceneGeometry; } } return MonitorSceneDefault; } /*void AssetParameterView::initKeyframeView() { if (m_mainKeyframeWidget) { m_mainKeyframeWidget->initMonitor(); } else { for (int i = 0; i < m_model->rowCount(); ++i) { QModelIndex index = m_model->index(i, 0); auto type = m_model->data(index, AssetParameterModel::TypeRole).value(); if (type == ParamType::Geometry) { return MonitorSceneGeometry; } } } }*/ void AssetParameterView::slotRefresh() { refresh(m_model->index(0, 0), m_model->index(m_model->rowCount() - 1, 0), {}); } bool AssetParameterView::keyframesAllowed() const { return m_mainKeyframeWidget != nullptr; } bool AssetParameterView::modelHideKeyframes() const { return m_mainKeyframeWidget != nullptr && !m_mainKeyframeWidget->keyframesVisible(); } void AssetParameterView::toggleKeyframes(bool enable) { if (m_mainKeyframeWidget) { m_mainKeyframeWidget->showKeyframes(enable); } } void AssetParameterView::slotDeletePreset() { QAction *ac = m_presetGroup->checkedAction(); if (!ac) { return; } QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/")); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId())); m_model->deletePreset(presetFile, ac->data().toString()); emit updatePresets(); } void AssetParameterView::slotUpdatePreset() { QAction *ac = m_presetGroup->checkedAction(); if (!ac) { return; } slotSavePreset(ac->data().toString()); } void AssetParameterView::slotSavePreset(QString presetName) { if (presetName.isEmpty()) { bool ok; presetName = QInputDialog::getText(this, i18n("Enter preset name"), i18n("Enter the name of this preset"), QLineEdit::Normal, QString(), &ok); if (!ok) return; } QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/")); if (!dir.exists()) { dir.mkpath(QStringLiteral(".")); } const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId())); m_model->savePreset(presetFile, presetName); emit updatePresets(presetName); } void AssetParameterView::slotLoadPreset() { auto *action = qobject_cast(sender()); if (!action) { return; } const QString presetName = action->data().toString(); QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/")); const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId())); const QVector> params = m_model->loadPreset(presetFile, presetName); auto *command = new AssetUpdateCommand(m_model, params); pCore->pushUndo(command); } QMenu *AssetParameterView::presetMenu() { return m_presetMenu; } diff --git a/src/effects/effectstack/model/effectstackmodel.cpp b/src/effects/effectstack/model/effectstackmodel.cpp index a51a0b106..53745833f 100644 --- a/src/effects/effectstack/model/effectstackmodel.cpp +++ b/src/effects/effectstack/model/effectstackmodel.cpp @@ -1,1199 +1,1199 @@ /*************************************************************************** * Copyright (C) 2017 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * 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 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * 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 "effectstackmodel.hpp" #include "assets/keyframes/model/keyframemodellist.hpp" #include "core.h" #include "doc/docundostack.hpp" #include "effectgroupmodel.hpp" #include "effectitemmodel.hpp" #include "effects/effectsrepository.hpp" #include "macros.hpp" #include "timeline2/model/timelinemodel.hpp" #include #include #include #include EffectStackModel::EffectStackModel(std::weak_ptr service, ObjectId ownerId, std::weak_ptr undo_stack) : AbstractTreeModel() , m_effectStackEnabled(true) , m_ownerId(std::move(ownerId)) , m_undoStack(std::move(undo_stack)) , m_lock(QReadWriteLock::Recursive) , m_loadingExisting(false) { m_masterService = std::move(service); } std::shared_ptr EffectStackModel::construct(std::weak_ptr service, ObjectId ownerId, std::weak_ptr undo_stack) { std::shared_ptr self(new EffectStackModel(std::move(service), ownerId, std::move(undo_stack))); self->rootItem = EffectGroupModel::construct(QStringLiteral("root"), self, true); return self; } void EffectStackModel::resetService(std::weak_ptr service) { QWriteLocker locker(&m_lock); m_masterService = std::move(service); m_childServices.clear(); // replant all effects in new service for (int i = 0; i < rootItem->childCount(); ++i) { std::static_pointer_cast(rootItem->child(i))->plant(m_masterService); } } void EffectStackModel::addService(std::weak_ptr service) { QWriteLocker locker(&m_lock); m_childServices.emplace_back(std::move(service)); for (int i = 0; i < rootItem->childCount(); ++i) { std::static_pointer_cast(rootItem->child(i))->plantClone(m_childServices.back()); } } void EffectStackModel::loadService(std::weak_ptr service) { QWriteLocker locker(&m_lock); m_childServices.emplace_back(std::move(service)); for (int i = 0; i < rootItem->childCount(); ++i) { std::static_pointer_cast(rootItem->child(i))->loadClone(m_childServices.back()); } } void EffectStackModel::removeService(const std::shared_ptr &service) { QWriteLocker locker(&m_lock); std::vector to_delete; for (int i = int(m_childServices.size()) - 1; i >= 0; --i) { auto ptr = m_childServices[uint(i)].lock(); if (service->get_int("_childid") == ptr->get_int("_childid")) { for (int j = 0; j < rootItem->childCount(); ++j) { std::static_pointer_cast(rootItem->child(j))->unplantClone(ptr); } to_delete.push_back(i); } } for (int i : to_delete) { m_childServices.erase(m_childServices.begin() + i); } } void EffectStackModel::removeCurrentEffect() { int ix = 0; if (auto ptr = m_masterService.lock()) { ix = ptr->get_int("kdenlive:activeeffect"); } if (ix < 0) { return; } std::shared_ptr effect = std::static_pointer_cast(rootItem->child(ix)); if (effect) { removeEffect(effect); } } void EffectStackModel::removeEffect(const std::shared_ptr &effect) { qDebug() << "* * ** REMOVING EFFECT FROM STACK!!!\n!!!!!!!!!"; QWriteLocker locker(&m_lock); Q_ASSERT(m_allItems.count(effect->getId()) > 0); int parentId = -1; if (auto ptr = effect->parentItem().lock()) parentId = ptr->getId(); int current = 0; if (auto srv = m_masterService.lock()) { current = srv->get_int("kdenlive:activeeffect"); if (current >= rootItem->childCount() - 1) { srv->set("kdenlive:activeeffect", --current); } } int currentRow = effect->row(); Fun undo = addItem_lambda(effect, parentId); if (currentRow != rowCount() - 1) { Fun move = moveItem_lambda(effect->getId(), currentRow, true); PUSH_LAMBDA(move, undo); } Fun redo = removeItem_lambda(effect->getId()); bool res = redo(); if (res) { int inFades = int(m_fadeIns.size()); int outFades = int(m_fadeOuts.size()); m_fadeIns.erase(effect->getId()); m_fadeOuts.erase(effect->getId()); inFades = int(m_fadeIns.size()) - inFades; outFades = int(m_fadeOuts.size()) - outFades; QString effectName = EffectsRepository::get()->getName(effect->getAssetId()); Fun update = [this, current, inFades, outFades]() { // Required to build the effect view if (current < 0 || rowCount() == 0) { // Stack is now empty emit dataChanged(QModelIndex(), QModelIndex(), {}); } else { QVector roles = {TimelineModel::EffectNamesRole}; if (inFades < 0) { roles << TimelineModel::FadeInRole; } if (outFades < 0) { roles << TimelineModel::FadeOutRole; } qDebug() << "// EMITTING UNDO DATA CHANGE: " << roles; emit dataChanged(QModelIndex(), QModelIndex(), roles); } // TODO: only update if effect is fade or keyframe /*if (inFades < 0) { pCore->updateItemModel(m_ownerId, QStringLiteral("fadein")); } else if (outFades < 0) { pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout")); }*/ pCore->updateItemKeyframes(m_ownerId); return true; }; Fun update2 = [this, inFades, outFades]() { // Required to build the effect view QVector roles = {TimelineModel::EffectNamesRole}; // TODO: only update if effect is fade or keyframe if (inFades < 0) { roles << TimelineModel::FadeInRole; } else if (outFades < 0) { roles << TimelineModel::FadeOutRole; } qDebug() << "// EMITTING REDO DATA CHANGE: " << roles; emit dataChanged(QModelIndex(), QModelIndex(), roles); pCore->updateItemKeyframes(m_ownerId); return true; }; update(); PUSH_LAMBDA(update, redo); PUSH_LAMBDA(update2, undo); PUSH_UNDO(undo, redo, i18n("Delete effect %1", effectName)); } else { qDebug() << "..........FAILED EFFECT DELETION"; } } bool EffectStackModel::copyXmlEffect(QDomElement effect) { std::function undo = []() { return true; }; std::function redo = []() { return true; }; bool result = fromXml(effect, undo, redo); if (result) { PUSH_UNDO(undo, redo, i18n("Copy effect")); } return result; } QDomElement EffectStackModel::toXml(QDomDocument &document) { QDomElement container = document.createElement(QStringLiteral("effects")); int currentIn = pCore->getItemIn(m_ownerId); container.setAttribute(QStringLiteral("parentIn"), currentIn); for (int i = 0; i < rootItem->childCount(); ++i) { std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(i)); QDomElement sub = document.createElement(QStringLiteral("effect")); sub.setAttribute(QStringLiteral("id"), sourceEffect->getAssetId()); int filterIn = sourceEffect->filter().get_int("in"); int filterOut = sourceEffect->filter().get_int("out"); if (filterOut > filterIn) { sub.setAttribute(QStringLiteral("in"), filterIn); sub.setAttribute(QStringLiteral("out"), filterOut); } QStringList passProps {QStringLiteral("disable"), QStringLiteral("kdenlive:collapsed")}; for (const QString ¶m : passProps) { int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData()); if (paramVal > 0) { Xml::setXmlProperty(sub, param, QString::number(paramVal)); } } QVector> params = sourceEffect->getAllParameters(); QLocale locale; for (const auto ¶m : params) { if (param.second.type() == QVariant::Double) { Xml::setXmlProperty(sub, param.first, locale.toString(param.second.toDouble())); } else { Xml::setXmlProperty(sub, param.first, param.second.toString()); } } container.appendChild(sub); } return container; } QDomElement EffectStackModel::rowToXml(int row, QDomDocument &document) { QDomElement container = document.createElement(QStringLiteral("effects")); if (row < 0 || row >= rootItem->childCount()) { return container; } int currentIn = pCore->getItemIn(m_ownerId); container.setAttribute(QStringLiteral("parentIn"), currentIn); std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(row)); QDomElement sub = document.createElement(QStringLiteral("effect")); sub.setAttribute(QStringLiteral("id"), sourceEffect->getAssetId()); int filterIn = sourceEffect->filter().get_int("in"); int filterOut = sourceEffect->filter().get_int("out"); if (filterOut > filterIn) { sub.setAttribute(QStringLiteral("in"), filterIn); sub.setAttribute(QStringLiteral("out"), filterOut); } QStringList passProps {QStringLiteral("disable"), QStringLiteral("kdenlive:collapsed")}; for (const QString ¶m : passProps) { int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData()); if (paramVal > 0) { Xml::setXmlProperty(sub, param, QString::number(paramVal)); } } QVector> params = sourceEffect->getAllParameters(); QLocale locale; for (const auto ¶m : params) { if (param.second.type() == QVariant::Double) { Xml::setXmlProperty(sub, param.first, locale.toString(param.second.toDouble())); } else { Xml::setXmlProperty(sub, param.first, param.second.toString()); } } container.appendChild(sub); return container; } bool EffectStackModel::fromXml(const QDomElement &effectsXml, Fun &undo, Fun &redo) { QDomNodeList nodeList = effectsXml.elementsByTagName(QStringLiteral("effect")); int parentIn = effectsXml.attribute(QStringLiteral("parentIn")).toInt(); qDebug()<<"// GOT PREVIOUS PARENTIN: "<getItemIn(m_ownerId); PlaylistState::ClipState state = pCore->getItemState(m_ownerId); for (int i = 0; i < nodeList.count(); ++i) { QDomElement node = nodeList.item(i).toElement(); const QString effectId = node.attribute(QStringLiteral("id")); EffectType type = EffectsRepository::get()->getType(effectId); bool isAudioEffect = type == EffectType::Audio || type == EffectType::CustomAudio; if (isAudioEffect) { if (state != PlaylistState::AudioOnly) { continue; } } else if (state != PlaylistState::VideoOnly) { continue; } bool effectEnabled = true; if (Xml::hasXmlProperty(node, QLatin1String("disable"))) { effectEnabled = Xml::getXmlProperty(node, QLatin1String("disable")).toInt() != 1; } auto effect = EffectItemModel::construct(effectId, shared_from_this(), effectEnabled); const QString in = node.attribute(QStringLiteral("in")); const QString out = node.attribute(QStringLiteral("out")); if (!out.isEmpty()) { effect->filter().set("in", in.toUtf8().constData()); effect->filter().set("out", out.toUtf8().constData()); } QStringList keyframeParams = effect->getKeyframableParameters(); QVector> parameters; QDomNodeList params = node.elementsByTagName(QStringLiteral("property")); for (int j = 0; j < params.count(); j++) { QDomElement pnode = params.item(j).toElement(); const QString pName = pnode.attribute(QStringLiteral("name")); if (pName == QLatin1String("in") || pName == QLatin1String("out")) { continue; } if (keyframeParams.contains(pName)) { // This is a keyframable parameter, fix offset QString pValue = KeyframeModel::getAnimationStringWithOffset(effect, pnode.text(), currentIn - parentIn); parameters.append(QPair(pName, QVariant(pValue))); } else { parameters.append(QPair(pName, QVariant(pnode.text()))); } } effect->setParameters(parameters); Fun local_undo = removeItem_lambda(effect->getId()); // TODO the parent should probably not always be the root Fun local_redo = addItem_lambda(effect, rootItem->getId()); effect->prepareKeyframes(); connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged); connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection); if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) { m_fadeIns.insert(effect->getId()); int duration = effect->filter().get_length() - 1; effect->filter().set("in", currentIn); effect->filter().set("out", currentIn + duration); } else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) { m_fadeOuts.insert(effect->getId()); int duration = effect->filter().get_length() - 1; int filterOut = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1; effect->filter().set("in", filterOut - duration); effect->filter().set("out", filterOut); } local_redo(); UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); } if (true) { Fun update = [this]() { emit dataChanged(QModelIndex(), QModelIndex(), {}); return true; }; update(); PUSH_LAMBDA(update, redo); PUSH_LAMBDA(update, undo); } return true; } bool EffectStackModel::copyEffect(const std::shared_ptr &sourceItem, PlaylistState::ClipState state) { QWriteLocker locker(&m_lock); if (sourceItem->childCount() > 0) { // TODO: group return false; } bool audioEffect = sourceItem->isAudio(); if (audioEffect) { if (state == PlaylistState::VideoOnly) { // This effect cannot be used return false; } } else if (state == PlaylistState::AudioOnly) { return false; } std::shared_ptr sourceEffect = std::static_pointer_cast(sourceItem); const QString effectId = sourceEffect->getAssetId(); bool enabled = sourceEffect->isEnabled(); auto effect = EffectItemModel::construct(effectId, shared_from_this(), enabled); effect->setParameters(sourceEffect->getAllParameters()); if (!enabled) { effect->filter().set("disable", 1); } effect->filter().set("in", sourceEffect->filter().get_int("in")); effect->filter().set("out", sourceEffect->filter().get_int("out")); Fun local_undo = removeItem_lambda(effect->getId()); // TODO the parent should probably not always be the root Fun local_redo = addItem_lambda(effect, rootItem->getId()); effect->prepareKeyframes(); connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged); connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection); QVector roles = {TimelineModel::EffectNamesRole}; if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) { m_fadeIns.insert(effect->getId()); int duration = effect->filter().get_length() - 1; int in = pCore->getItemIn(m_ownerId); effect->filter().set("in", in); effect->filter().set("out", in + duration); roles << TimelineModel::FadeInRole; } else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) { m_fadeOuts.insert(effect->getId()); int duration = effect->filter().get_length() - 1; int out = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1; effect->filter().set("in", out - duration); effect->filter().set("out", out); roles << TimelineModel::FadeOutRole; } bool res = local_redo(); if (res) { Fun update = [this, roles]() { emit dataChanged(QModelIndex(), QModelIndex(), roles); return true; }; } return res; } bool EffectStackModel::appendEffect(const QString &effectId, bool makeCurrent) { QWriteLocker locker(&m_lock); auto effect = EffectItemModel::construct(effectId, shared_from_this()); PlaylistState::ClipState state = pCore->getItemState(m_ownerId); if (effect->isAudio()) { if (state == PlaylistState::VideoOnly) { // Cannot add effect to this clip return false; } } else if (state == PlaylistState::AudioOnly) { // Cannot add effect to this clip return false; } Fun undo = removeItem_lambda(effect->getId()); // TODO the parent should probably not always be the root Fun redo = addItem_lambda(effect, rootItem->getId()); effect->prepareKeyframes(); connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged); connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection); int currentActive = getActiveEffect(); if (makeCurrent) { if (auto srvPtr = m_masterService.lock()) { srvPtr->set("kdenlive:activeeffect", rowCount()); } } bool res = redo(); if (res) { int inFades = 0; int outFades = 0; if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) { int duration = effect->filter().get_length() - 1; int in = pCore->getItemIn(m_ownerId); effect->filter().set("in", in); effect->filter().set("out", in + duration); inFades++; } else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) { /*int duration = effect->filter().get_length() - 1; int out = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1; effect->filter().set("in", out - duration); effect->filter().set("out", out);*/ outFades++; } else if (m_ownerId.first == ObjectType::TimelineTrack) { effect->filter().set("out", pCore->getItemDuration(m_ownerId)); } QString effectName = EffectsRepository::get()->getName(effectId); Fun update = [this, inFades, outFades]() { // TODO: only update if effect is fade or keyframe QVector roles = {TimelineModel::EffectNamesRole}; if (inFades > 0) { roles << TimelineModel::FadeInRole; } else if (outFades > 0) { roles << TimelineModel::FadeOutRole; } pCore->updateItemKeyframes(m_ownerId); emit dataChanged(QModelIndex(), QModelIndex(), roles); return true; }; update(); PUSH_LAMBDA(update, redo); PUSH_LAMBDA(update, undo); - PUSH_UNDO(undo, redo, i18n("Add effect %1").arg(i18n(effectName.toUtf8().data()))); + PUSH_UNDO(undo, redo, i18n("Add effect %1", i18n(effectName.toUtf8().data()))); } else if (makeCurrent) { if (auto srvPtr = m_masterService.lock()) { srvPtr->set("kdenlive:activeeffect", currentActive); } } return res; } bool EffectStackModel::adjustStackLength(bool adjustFromEnd, int oldIn, int oldDuration, int newIn, int duration, int offset, Fun &undo, Fun &redo, bool logUndo) { QWriteLocker locker(&m_lock); const int fadeInDuration = getFadePosition(true); const int fadeOutDuration = getFadePosition(false); int out = newIn + duration; for (const auto &leaf : rootItem->getLeaves()) { std::shared_ptr item = std::static_pointer_cast(leaf); if (item->effectItemType() == EffectItemType::Group) { // probably an empty group, ignore continue; } std::shared_ptr effect = std::static_pointer_cast(leaf); if (fadeInDuration > 0 && m_fadeIns.count(leaf->getId()) > 0) { int oldEffectIn = qMax(0, effect->filter().get_in()); int oldEffectOut = effect->filter().get_out(); qDebug() << "--previous effect: " << oldEffectIn << "-" << oldEffectOut; int effectDuration = qMin(effect->filter().get_length() - 1, duration); if (!adjustFromEnd && (oldIn != newIn || duration != oldDuration)) { // Clip start was resized, adjust effect in / out Fun operation = [effect, newIn, effectDuration, logUndo]() { effect->setParameter(QStringLiteral("in"), newIn, false); effect->setParameter(QStringLiteral("out"), newIn + effectDuration, logUndo); qDebug() << "--new effect: " << newIn << "-" << newIn + effectDuration; return true; }; bool res = operation(); if (!res) { return false; } Fun reverse = [effect, oldEffectIn, oldEffectOut, logUndo]() { effect->setParameter(QStringLiteral("in"), oldEffectIn, false); effect->setParameter(QStringLiteral("out"), oldEffectOut, logUndo); return true; }; PUSH_LAMBDA(operation, redo); PUSH_LAMBDA(reverse, undo); } else if (effectDuration < oldEffectOut - oldEffectIn || (logUndo && effect->filter().get_int("_refout") > 0)) { // Clip length changed, shorter than effect length so resize int referenceEffectOut = effect->filter().get_int("_refout"); if (referenceEffectOut <= 0) { referenceEffectOut = oldEffectOut; effect->filter().set("_refout", referenceEffectOut); } Fun operation = [effect, oldEffectIn, effectDuration, logUndo]() { effect->setParameter(QStringLiteral("out"), oldEffectIn + effectDuration, logUndo); return true; }; bool res = operation(); if (!res) { return false; } if (logUndo) { Fun reverse = [effect, referenceEffectOut]() { effect->setParameter(QStringLiteral("out"), referenceEffectOut, true); effect->filter().set("_refout", (char *)nullptr); return true; }; PUSH_LAMBDA(operation, redo); PUSH_LAMBDA(reverse, undo); } } } else if (fadeOutDuration > 0 && m_fadeOuts.count(leaf->getId()) > 0) { int effectDuration = qMin(fadeOutDuration, duration); int newFadeIn = out - effectDuration; int oldFadeIn = effect->filter().get_int("in"); int oldOut = effect->filter().get_int("out"); int referenceEffectIn = effect->filter().get_int("_refin"); if (referenceEffectIn <= 0) { referenceEffectIn = oldFadeIn; effect->filter().set("_refin", referenceEffectIn); } Fun operation = [effect, newFadeIn, out, logUndo]() { effect->setParameter(QStringLiteral("in"), newFadeIn, false); effect->setParameter(QStringLiteral("out"), out, logUndo); return true; }; bool res = operation(); if (!res) { return false; } if (logUndo) { Fun reverse = [effect, referenceEffectIn, oldOut]() { effect->setParameter(QStringLiteral("in"), referenceEffectIn, false); effect->setParameter(QStringLiteral("out"), oldOut, true); effect->filter().set("_refin", (char *)nullptr); return true; }; PUSH_LAMBDA(operation, redo); PUSH_LAMBDA(reverse, undo); } } else { // Not a fade effect, check for keyframes std::shared_ptr keyframes = effect->getKeyframeModel(); if (keyframes != nullptr) { // Effect has keyframes, update these keyframes->resizeKeyframes(oldIn, oldIn + oldDuration - 1, newIn, out - 1, offset, adjustFromEnd, undo, redo); QModelIndex index = getIndexFromItem(effect); Fun refresh = [effect, index]() { effect->dataChanged(index, index, QVector()); return true; }; refresh(); PUSH_LAMBDA(refresh, redo); PUSH_LAMBDA(refresh, undo); } else { qDebug() << "// NULL Keyframes---------"; } } } return true; } bool EffectStackModel::adjustFadeLength(int duration, bool fromStart, bool audioFade, bool videoFade, bool logUndo) { QWriteLocker locker(&m_lock); if (fromStart) { // Fade in if (m_fadeIns.empty()) { if (audioFade) { appendEffect(QStringLiteral("fadein")); } if (videoFade) { appendEffect(QStringLiteral("fade_from_black")); } } QList indexes; auto ptr = m_masterService.lock(); int in = 0; if (ptr) { in = ptr->get_int("in"); } int oldDuration = -1; for (int i = 0; i < rootItem->childCount(); ++i) { if (m_fadeIns.count(std::static_pointer_cast(rootItem->child(i))->getId()) > 0) { std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i)); if (oldDuration == -1) { oldDuration = effect->filter().get_length(); } effect->filter().set("in", in); duration = qMin(pCore->getItemDuration(m_ownerId), duration); effect->filter().set("out", in + duration); indexes << getIndexFromItem(effect); } } if (!indexes.isEmpty()) { emit dataChanged(indexes.first(), indexes.last(), QVector()); pCore->updateItemModel(m_ownerId, QStringLiteral("fadein")); if (videoFade) { int min = pCore->getItemPosition(m_ownerId); QSize range(min, min + qMax(duration, oldDuration)); pCore->refreshProjectRange(range); if (logUndo) { pCore->invalidateRange(range); } } } } else { // Fade out if (m_fadeOuts.empty()) { if (audioFade) { appendEffect(QStringLiteral("fadeout")); } if (videoFade) { appendEffect(QStringLiteral("fade_to_black")); } } int in = 0; auto ptr = m_masterService.lock(); if (ptr) { in = ptr->get_int("in"); } int itemDuration = pCore->getItemDuration(m_ownerId); int out = in + itemDuration; int oldDuration = -1; QList indexes; for (int i = 0; i < rootItem->childCount(); ++i) { if (m_fadeOuts.count(std::static_pointer_cast(rootItem->child(i))->getId()) > 0) { std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i)); if (oldDuration == -1) { oldDuration = effect->filter().get_length(); } effect->filter().set("out", out - 1); duration = qMin(itemDuration, duration); effect->filter().set("in", out - duration); indexes << getIndexFromItem(effect); } } if (!indexes.isEmpty()) { emit dataChanged(indexes.first(), indexes.last(), QVector()); pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout")); if (videoFade) { int min = pCore->getItemPosition(m_ownerId); QSize range(min + itemDuration - qMax(duration, oldDuration), min + itemDuration); pCore->refreshProjectRange(range); if (logUndo) { pCore->invalidateRange(range); } } } } return true; } int EffectStackModel::getFadePosition(bool fromStart) { QWriteLocker locker(&m_lock); if (fromStart) { if (m_fadeIns.empty()) { return 0; } for (int i = 0; i < rootItem->childCount(); ++i) { if (*(m_fadeIns.begin()) == std::static_pointer_cast(rootItem->child(i))->getId()) { std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i)); return effect->filter().get_length() - 1; } } } else { if (m_fadeOuts.empty()) { return 0; } for (int i = 0; i < rootItem->childCount(); ++i) { if (*(m_fadeOuts.begin()) == std::static_pointer_cast(rootItem->child(i))->getId()) { std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i)); return effect->filter().get_length() - 1; } } } return 0; } bool EffectStackModel::removeFade(bool fromStart) { QWriteLocker locker(&m_lock); std::vector toRemove; for (int i = 0; i < rootItem->childCount(); ++i) { if ((fromStart && m_fadeIns.count(std::static_pointer_cast(rootItem->child(i))->getId()) > 0) || (!fromStart && m_fadeOuts.count(std::static_pointer_cast(rootItem->child(i))->getId()) > 0)) { toRemove.push_back(i); } } for (int i : toRemove) { std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i)); removeEffect(effect); } return true; } void EffectStackModel::moveEffect(int destRow, const std::shared_ptr &item) { QWriteLocker locker(&m_lock); Q_ASSERT(m_allItems.count(item->getId()) > 0); int oldRow = item->row(); Fun undo = moveItem_lambda(item->getId(), oldRow); Fun redo = moveItem_lambda(item->getId(), destRow); bool res = redo(); if (res) { Fun update = [this]() { this->dataChanged(QModelIndex(), QModelIndex(), {TimelineModel::EffectNamesRole}); return true; }; update(); UPDATE_UNDO_REDO(update, update, undo, redo); auto effectId = std::static_pointer_cast(item)->getAssetId(); QString effectName = EffectsRepository::get()->getName(effectId); PUSH_UNDO(undo, redo, i18n("Move effect %1").arg(i18n(effectName.toUtf8().data()))); } } void EffectStackModel::registerItem(const std::shared_ptr &item) { QWriteLocker locker(&m_lock); // qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting effect"; QModelIndex ix; if (!item->isRoot()) { auto effectItem = std::static_pointer_cast(item); if (!m_loadingExisting) { // qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting effect in " << m_childServices.size(); effectItem->plant(m_masterService); for (const auto &service : m_childServices) { // qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting CLONE effect in " << (void *)service.lock().get(); effectItem->plantClone(service); } } effectItem->setEffectStackEnabled(m_effectStackEnabled); const QString &effectId = effectItem->getAssetId(); if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) { m_fadeIns.insert(effectItem->getId()); } else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) { m_fadeOuts.insert(effectItem->getId()); } ix = getIndexFromItem(effectItem); if (!effectItem->isAudio() && !m_loadingExisting) { pCore->refreshProjectItem(m_ownerId); pCore->invalidateItem(m_ownerId); } } AbstractTreeModel::registerItem(item); } void EffectStackModel::deregisterItem(int id, TreeItem *item) { QWriteLocker locker(&m_lock); if (!item->isRoot()) { auto effectItem = static_cast(item); effectItem->unplant(m_masterService); for (const auto &service : m_childServices) { effectItem->unplantClone(service); } if (!effectItem->isAudio()) { pCore->refreshProjectItem(m_ownerId); pCore->invalidateItem(m_ownerId); } } AbstractTreeModel::deregisterItem(id, item); } void EffectStackModel::setEffectStackEnabled(bool enabled) { QWriteLocker locker(&m_lock); m_effectStackEnabled = enabled; // Recursively updates children states for (int i = 0; i < rootItem->childCount(); ++i) { std::static_pointer_cast(rootItem->child(i))->setEffectStackEnabled(enabled); } emit dataChanged(QModelIndex(), QModelIndex(), {TimelineModel::EffectsEnabledRole}); emit enabledStateChanged(); } std::shared_ptr EffectStackModel::getEffectStackRow(int row, const std::shared_ptr &parentItem) { return std::static_pointer_cast(parentItem ? parentItem->child(row) : rootItem->child(row)); } bool EffectStackModel::importEffects(const std::shared_ptr &sourceStack, PlaylistState::ClipState state) { QWriteLocker locker(&m_lock); // TODO: manage fades, keyframes if clips don't have same size / in point bool found = false; for (int i = 0; i < sourceStack->rowCount(); i++) { auto item = sourceStack->getEffectStackRow(i); // NO undo. this should only be used on project opening if (copyEffect(item, state)) { found = true; } } if (found) { modelChanged(); } return found; } void EffectStackModel::importEffects(const std::weak_ptr &service, PlaylistState::ClipState state, bool alreadyExist) { QWriteLocker locker(&m_lock); m_loadingExisting = alreadyExist; if (auto ptr = service.lock()) { for (int i = 0; i < ptr->filter_count(); i++) { std::unique_ptr filter(ptr->filter(i)); if (filter->get("kdenlive_id") == nullptr) { // don't consider internal MLT stuff continue; } const QString effectId = qstrdup(filter->get("kdenlive_id")); // The MLT filter already exists, use it directly to create the effect std::shared_ptr effect; if (alreadyExist) { // effect is already plugged in the service effect = EffectItemModel::construct(std::move(filter), shared_from_this()); } else { // duplicate effect std::unique_ptr asset = EffectsRepository::get()->getEffect(effectId); asset->inherit(*(filter)); effect = EffectItemModel::construct(std::move(asset), shared_from_this()); } if (effect->isAudio()) { if (state == PlaylistState::VideoOnly) { // Don't import effect continue; } } else if (state == PlaylistState::AudioOnly) { // Don't import effect continue; } connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged); connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection); Fun redo = addItem_lambda(effect, rootItem->getId()); effect->prepareKeyframes(); if (redo()) { if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) { m_fadeIns.insert(effect->getId()); int clipIn = ptr->get_int("in"); if (effect->filter().get_int("in") != clipIn) { // Broken fade, fix int filterLength = effect->filter().get_length() - 1; effect->filter().set("in", clipIn); effect->filter().set("out", clipIn + filterLength); } } else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) { m_fadeOuts.insert(effect->getId()); int clipOut = ptr->get_int("out"); if (effect->filter().get_int("out") != clipOut) { // Broken fade, fix int filterLength = effect->filter().get_length() - 1; effect->filter().set("in", clipOut - filterLength); effect->filter().set("out", clipOut); } } } } } m_loadingExisting = false; modelChanged(); } void EffectStackModel::setActiveEffect(int ix) { QWriteLocker locker(&m_lock); if (auto ptr = m_masterService.lock()) { ptr->set("kdenlive:activeeffect", ix); } pCore->updateItemKeyframes(m_ownerId); } int EffectStackModel::getActiveEffect() const { QWriteLocker locker(&m_lock); if (auto ptr = m_masterService.lock()) { return ptr->get_int("kdenlive:activeeffect"); } return 0; } void EffectStackModel::slotCreateGroup(const std::shared_ptr &childEffect) { QWriteLocker locker(&m_lock); auto groupItem = EffectGroupModel::construct(QStringLiteral("group"), shared_from_this()); rootItem->appendChild(groupItem); groupItem->appendChild(childEffect); } ObjectId EffectStackModel::getOwnerId() const { return m_ownerId; } bool EffectStackModel::checkConsistency() { if (!AbstractTreeModel::checkConsistency()) { return false; } std::vector> allFilters; // We do a DFS on the tree to retrieve all the filters std::stack> stck; stck.push(std::static_pointer_cast(rootItem)); while (!stck.empty()) { auto current = stck.top(); stck.pop(); if (current->effectItemType() == EffectItemType::Effect) { if (current->childCount() > 0) { qDebug() << "ERROR: Found an effect with children"; return false; } allFilters.push_back(std::static_pointer_cast(current)); continue; } for (int i = current->childCount() - 1; i >= 0; --i) { stck.push(std::static_pointer_cast(current->child(i))); } } for (const auto &service : m_childServices) { auto ptr = service.lock(); if (!ptr) { qDebug() << "ERROR: unavailable service"; return false; } // MLT inserts some default normalizer filters that are not managed by Kdenlive, which explains why the filter count is not equal int kdenliveFilterCount = 0; for (int i = 0; i < ptr->filter_count(); i++) { std::shared_ptr filt(ptr->filter(i)); if (filt->get("kdenlive_id") != nullptr) { kdenliveFilterCount++; } // qDebug() << "FILTER: "<filter(i)->get("mlt_service"); } if (kdenliveFilterCount != (int)allFilters.size()) { qDebug() << "ERROR: Wrong filter count: " << kdenliveFilterCount << " = " << allFilters.size(); return false; } int ct = 0; for (uint i = 0; i < allFilters.size(); ++i) { while (ptr->filter(ct)->get("kdenlive_id") == nullptr && ct < ptr->filter_count()) { ct++; } auto mltFilter = ptr->filter(ct); auto currentFilter = allFilters[i]->filter(); if (QString(currentFilter.get("mlt_service")) != QLatin1String(mltFilter->get("mlt_service"))) { qDebug() << "ERROR: filter " << i << "differ: " << ct << ", " << currentFilter.get("mlt_service") << " = " << mltFilter->get("mlt_service"); return false; } QVector> params = allFilters[i]->getAllParameters(); for (const auto &val : params) { // Check parameters values if (val.second != QVariant(mltFilter->get(val.first.toUtf8().constData()))) { qDebug() << "ERROR: filter " << i << "PARAMETER MISMATCH: " << val.first << " = " << val.second << " != " << mltFilter->get(val.first.toUtf8().constData()); return false; } } ct++; } } return true; } void EffectStackModel::adjust(const QString &effectId, const QString &effectName, double value) { QWriteLocker locker(&m_lock); for (int i = 0; i < rootItem->childCount(); ++i) { std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(i)); if (effectId == sourceEffect->getAssetId()) { sourceEffect->setParameter(effectName, QString::number(value)); return; } } } std::shared_ptr EffectStackModel::getAssetModelById(const QString &effectId) { QWriteLocker locker(&m_lock); for (int i = 0; i < rootItem->childCount(); ++i) { std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(i)); if (effectId == sourceEffect->getAssetId()) { return std::static_pointer_cast(sourceEffect); } } return nullptr; } bool EffectStackModel::hasFilter(const QString &effectId) const { READ_LOCK(); return rootItem->accumulate_const(false, [effectId](bool b, std::shared_ptr it) { if (b) return true; auto item = std::static_pointer_cast(it); if (item->effectItemType() == EffectItemType::Group) { return false; } auto sourceEffect = std::static_pointer_cast(it); return effectId == sourceEffect->getAssetId(); }); } double EffectStackModel::getFilterParam(const QString &effectId, const QString ¶mName) { READ_LOCK(); for (int i = 0; i < rootItem->childCount(); ++i) { std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(i)); if (effectId == sourceEffect->getAssetId()) { return sourceEffect->filter().get_double(paramName.toUtf8().constData()); } } return 0.0; } KeyframeModel *EffectStackModel::getEffectKeyframeModel() { if (rootItem->childCount() == 0) return nullptr; int ix = 0; if (auto ptr = m_masterService.lock()) { ix = ptr->get_int("kdenlive:activeeffect"); } if (ix < 0 || ix >= rootItem->childCount()) { return nullptr; } std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(ix)); std::shared_ptr listModel = sourceEffect->getKeyframeModel(); if (listModel) { return listModel->getKeyModel(); } return nullptr; } void EffectStackModel::replugEffect(const std::shared_ptr &asset) { QWriteLocker locker(&m_lock); auto effectItem = std::static_pointer_cast(asset); int oldRow = effectItem->row(); int count = rowCount(); for (int ix = oldRow; ix < count; ix++) { auto item = std::static_pointer_cast(rootItem->child(ix)); item->unplant(m_masterService); for (const auto &service : m_childServices) { item->unplantClone(service); } } std::unique_ptr effect = EffectsRepository::get()->getEffect(effectItem->getAssetId()); effect->inherit(effectItem->filter()); effectItem->resetAsset(std::move(effect)); for (int ix = oldRow; ix < count; ix++) { auto item = std::static_pointer_cast(rootItem->child(ix)); item->plant(m_masterService); for (const auto &service : m_childServices) { item->plantClone(service); } } } void EffectStackModel::cleanFadeEffects(bool outEffects, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); const auto &toDelete = outEffects ? m_fadeOuts : m_fadeIns; for (int id : toDelete) { auto effect = std::static_pointer_cast(getItemById(id)); Fun operation = removeItem_lambda(id); if (operation()) { Fun reverse = addItem_lambda(effect, rootItem->getId()); UPDATE_UNDO_REDO(operation, reverse, undo, redo); } } if (!toDelete.empty()) { Fun updateRedo = [this, toDelete, outEffects]() { for (int id : toDelete) { if (outEffects) { m_fadeOuts.erase(id); } else { m_fadeIns.erase(id); } } QVector roles = {TimelineModel::EffectNamesRole}; roles << (outEffects ? TimelineModel::FadeOutRole : TimelineModel::FadeInRole); emit dataChanged(QModelIndex(), QModelIndex(), roles); pCore->updateItemKeyframes(m_ownerId); return true; }; updateRedo(); PUSH_LAMBDA(updateRedo, redo); } } const QString EffectStackModel::effectNames() const { QStringList effects; for (int i = 0; i < rootItem->childCount(); ++i) { effects.append(i18n(EffectsRepository::get()->getName(std::static_pointer_cast(rootItem->child(i))->getAssetId()).toUtf8().data())); } return effects.join(QLatin1Char('/')); } bool EffectStackModel::isStackEnabled() const { return m_effectStackEnabled; } bool EffectStackModel::addEffectKeyFrame(int frame, double normalisedVal) { if (rootItem->childCount() == 0) return false; int ix = 0; if (auto ptr = m_masterService.lock()) { ix = ptr->get_int("kdenlive:activeeffect"); } if (ix < 0) { return false; } std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(ix)); std::shared_ptr listModel = sourceEffect->getKeyframeModel(); if (m_ownerId.first == ObjectType::TimelineTrack) { sourceEffect->filter().set("out", pCore->getItemDuration(m_ownerId)); } return listModel->addKeyframe(frame, normalisedVal); } bool EffectStackModel::removeKeyFrame(int frame) { if (rootItem->childCount() == 0) return false; int ix = 0; if (auto ptr = m_masterService.lock()) { ix = ptr->get_int("kdenlive:activeeffect"); } if (ix < 0) { return false; } std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(ix)); std::shared_ptr listModel = sourceEffect->getKeyframeModel(); return listModel->removeKeyframe(GenTime(frame, pCore->getCurrentFps())); } bool EffectStackModel::updateKeyFrame(int oldFrame, int newFrame, QVariant normalisedVal) { if (rootItem->childCount() == 0) return false; int ix = 0; if (auto ptr = m_masterService.lock()) { ix = ptr->get_int("kdenlive:activeeffect"); } if (ix < 0) { return false; } std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(ix)); std::shared_ptr listModel = sourceEffect->getKeyframeModel(); if (m_ownerId.first == ObjectType::TimelineTrack) { sourceEffect->filter().set("out", pCore->getItemDuration(m_ownerId)); } return listModel->updateKeyframe(GenTime(oldFrame, pCore->getCurrentFps()), GenTime(newFrame, pCore->getCurrentFps()), std::move(normalisedVal)); } diff --git a/src/project/clipstabilize.cpp b/src/project/clipstabilize.cpp index f37c348c8..dbf730fa0 100644 --- a/src/project/clipstabilize.cpp +++ b/src/project/clipstabilize.cpp @@ -1,234 +1,155 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * Copyright (C) 2011 by Marco Gittler (marco@gitma.de) * * * * 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 2 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "clipstabilize.h" #include "bin/projectclip.h" #include "bin/projectitemmodel.h" #include "core.h" #include "widgets/doublewidget.h" #include "widgets/positionwidget.h" +#include "assets/view/assetparameterview.hpp" +#include "assets/model/assetparametermodel.hpp" +#include "effects/effectsrepository.hpp" #include "kdenlivesettings.h" #include #include #include ClipStabilize::ClipStabilize(const std::vector &binIds, QString filterName, int out, QWidget *parent) : QDialog(parent) , m_filtername(std::move(filterName)) , m_binIds(binIds) , m_vbox(nullptr) + , m_assetModel(nullptr) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); setupUi(this); setWindowTitle(i18n("Stabilize Clip")); auto_add->setText(i18np("Add clip to project", "Add clips to project", m_binIds.size())); auto_add->setChecked(KdenliveSettings::add_new_clip()); // QString stylesheet = EffectStackView2::getStyleSheet(); // setStyleSheet(stylesheet); Q_ASSERT(binIds.size() > 0); auto firstBinClip = pCore->projectItemModel()->getClipByBinID(m_binIds.front()); auto firstUrl = firstBinClip->url(); if (m_binIds.size() == 1) { QString newFile = firstUrl; newFile.append(QStringLiteral(".mlt")); dest_url->setMode(KFile::File); dest_url->setUrl(QUrl(newFile)); } else { label_dest->setText(i18n("Destination folder")); dest_url->setMode(KFile::Directory | KFile::ExistingOnly); dest_url->setUrl(QUrl(firstUrl).adjusted(QUrl::RemoveFilename)); } - + m_vbox = new QVBoxLayout(optionsbox); if (m_filtername == QLatin1String("vidstab") || m_filtername == QLatin1String("videostab2")) { - m_fixedParams[QStringLiteral("algo")] = QStringLiteral("1"); - m_fixedParams[QStringLiteral("relative")] = QStringLiteral("1"); - fillParameters( - QStringList() << QStringLiteral("accuracy,type,int,value,8,min,1,max,10,tooltip,Accuracy of Shakiness detection") - << QStringLiteral("shakiness,type,int,value,4,min,1,max,10,tooltip,How shaky is the Video") - << QStringLiteral("stepsize,type,int,value,6,min,0,max,100,tooltip,Stepsize of Detection process minimum around") - << QStringLiteral("mincontrast,type,double,value,0.3,min,0,max,1,factor,1,decimals,2,tooltip,Below this Contrast Field is discarded") - << QStringLiteral("smoothing,type,int,value,10,min,0,max,100,tooltip,number of frames for lowpass filtering") - << QStringLiteral("maxshift,type,int,value,-1,min,-1,max,1000,tooltip,max number of pixels to shift") - << QStringLiteral("maxangle,type,double,value,-1,min,-1,max,3.14,decimals,2,tooltip,max angle to rotate (in rad)") - << QStringLiteral("crop,type,bool,value,0,min,0,max,1,tooltip,0 = keep border 1 = black background") - << QStringLiteral("zoom,type,int,value,0,min,-500,max,500,tooltip,additional zoom during transform") - << QStringLiteral("optzoom,type,bool,value,1,min,0,max,1,tooltip,use optimal zoom (calculated from transforms)") - << QStringLiteral("sharpen,type,double,value,0.8,min,0,max,1,decimals,1,tooltip,sharpen transformed image") - << QStringLiteral("tripod,type,position,value,0,min,0,max,100000,tooltip,reference frame")); - - } else if (m_filtername == QLatin1String("videostab")) { - fillParameters(QStringList(QStringLiteral("shutterangle,type,int,value,0,min,0,max,180,tooltip,Angle that Images could be maximum rotated"))); + AssetParameterView *view = new AssetParameterView(this); + std::unique_ptr asset = EffectsRepository::get()->getEffect(m_filtername); + auto prop = std::make_unique(asset->get_properties()); + QDomElement xml = EffectsRepository::get()->getXml(m_filtername); + m_assetModel.reset(new AssetParameterModel(std::move(prop), xml, m_filtername, {ObjectType::NoItem, -1})); + view->setModel(m_assetModel, QSize(1920, 1080)); + m_vbox->addWidget(view); } connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &ClipStabilize::slotValidate); - - m_vbox = new QVBoxLayout(optionsbox); - QHashIterator> hi(m_ui_params); - m_tc.setFormat(KdenliveSettings::project_fps()); - while (hi.hasNext()) { - hi.next(); - QHash val = hi.value(); - if (val[QStringLiteral("type")] == QLatin1String("int") || val[QStringLiteral("type")] == QLatin1String("double")) { - DoubleWidget *dbl = new DoubleWidget(hi.key() /*name*/, val[QStringLiteral("value")].toDouble(), val[QStringLiteral("min")].toDouble(), - val[QStringLiteral("max")].toDouble(), val[QStringLiteral("value")].toDouble(), 1, - /*default*/ - QString(), /*comment*/ - 0 /*id*/, QString(), /*suffix*/ - val[QStringLiteral("decimals")] != QString() ? val[QStringLiteral("decimals")].toInt() : 0, this); - dbl->setObjectName(hi.key()); - dbl->setToolTip(val[QStringLiteral("tooltip")]); - connect(dbl, &DoubleWidget::valueChanged, this, &ClipStabilize::slotUpdateParams); - m_vbox->addWidget(dbl); - } else if (val[QStringLiteral("type")] == QLatin1String("bool")) { - auto *ch = new QCheckBox(hi.key(), this); - ch->setCheckState(val[QStringLiteral("value")] == QLatin1String("0") ? Qt::Unchecked : Qt::Checked); - ch->setObjectName(hi.key()); - connect(ch, &QCheckBox::stateChanged, this, &ClipStabilize::slotUpdateParams); - ch->setToolTip(val[QStringLiteral("tooltip")]); - m_vbox->addWidget(ch); - } else if (val[QStringLiteral("type")] == QLatin1String("position")) { - PositionWidget *posedit = new PositionWidget(hi.key(), 0, 0, out, m_tc, QString(), this); - posedit->setToolTip(val[QStringLiteral("tooltip")]); - posedit->setObjectName(hi.key()); - m_vbox->addWidget(posedit); - connect(posedit, &PositionWidget::valueChanged, this, &ClipStabilize::slotUpdateParams); - } - } adjustSize(); } ClipStabilize::~ClipStabilize() { /*if (m_stabilizeProcess.state() != QProcess::NotRunning) { m_stabilizeProcess.close(); }*/ KdenliveSettings::setAdd_new_clip(auto_add->isChecked()); } std::unordered_map ClipStabilize::filterParams() const { + QVector> result = m_assetModel->getAllParameters(); std::unordered_map params; + QLocale locale; - for (const auto &it : m_fixedParams) { - params[it.first] = it.second; - } - - QHashIterator> it(m_ui_params); - while (it.hasNext()) { - it.next(); - params[it.key()] = it.value().value(QStringLiteral("value")); + for (const auto &it : result) { + if (it.second.type() == QVariant::Double) { + params[it.first] = locale.toString(it.second.toDouble()); + } else { + params[it.first] = it.second.toString(); + } } return params; } QString ClipStabilize::filterName() const { return m_filtername; } QString ClipStabilize::destination() const { QString path = dest_url->url().toLocalFile(); if (m_binIds.size() > 1 && !path.endsWith(QDir::separator())) { path.append(QDir::separator()); } return path; } QString ClipStabilize::desc() const { return i18n("Stabilize clip"); } -void ClipStabilize::slotUpdateParams() -{ - for (int i = 0; i < m_vbox->count(); ++i) { - QWidget *w = m_vbox->itemAt(i)->widget(); - QString name = w->objectName(); - if (!name.isEmpty() && m_ui_params.contains(name)) { - if (m_ui_params[name][QStringLiteral("type")] == QLatin1String("int") || m_ui_params[name][QStringLiteral("type")] == QLatin1String("double")) { - auto *dbl = static_cast(w); - m_ui_params[name][QStringLiteral("value")] = QString::number((double)(dbl->getValue())); - } else if (m_ui_params[name][QStringLiteral("type")] == QLatin1String("bool")) { - auto *ch = (QCheckBox *)w; - m_ui_params[name][QStringLiteral("value")] = ch->checkState() == Qt::Checked ? QStringLiteral("1") : QStringLiteral("0"); - } else if (m_ui_params[name][QStringLiteral("type")] == QLatin1String("position")) { - auto *pos = (PositionWidget *)w; - m_ui_params[name][QStringLiteral("value")] = QString::number(pos->getPosition()); - } - } - } -} - bool ClipStabilize::autoAddClip() const { return auto_add->isChecked(); } -void ClipStabilize::fillParameters(QStringList lst) -{ - - m_ui_params.clear(); - while (!lst.isEmpty()) { - QString vallist = lst.takeFirst(); - QStringList cont = vallist.split(QLatin1Char(',')); - QString name = cont.takeFirst(); - while (!cont.isEmpty()) { - QString valname = cont.takeFirst(); - QString val; - if (!cont.isEmpty()) { - val = cont.takeFirst(); - } - m_ui_params[name][valname] = val; - } - } -} - void ClipStabilize::slotValidate() { if (m_binIds.size() == 1) { if (QFile::exists(dest_url->url().toLocalFile())) { if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", dest_url->url().toLocalFile())) == KMessageBox::No) { return; } } } else { QDir folder(dest_url->url().toLocalFile()); QStringList existingFiles; for (const QString &binId : m_binIds) { auto binClip = pCore->projectItemModel()->getClipByBinID(binId); auto url = binClip->url(); if (folder.exists(url + QStringLiteral(".mlt"))) { existingFiles.append(folder.absoluteFilePath(url + QStringLiteral(".mlt"))); } } if (!existingFiles.isEmpty()) { if (KMessageBox::warningContinueCancelList(this, i18n("The stabilize job will overwrite the following files:"), existingFiles) == KMessageBox::Cancel) { return; } } } accept(); } diff --git a/src/project/clipstabilize.h b/src/project/clipstabilize.h index a4e5331e7..8bcd0b92d 100644 --- a/src/project/clipstabilize.h +++ b/src/project/clipstabilize.h @@ -1,65 +1,64 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * Copyright (C) 2011 by Marco Gittler (marco@gitma.de) * * * * 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 2 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, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #ifndef CLIPSTABILIZE_H #define CLIPSTABILIZE_H #include "definitions.h" #include "timecode.h" #include "ui_clipstabilize_ui.h" #include #include +class AssetParameterModel; + class ClipStabilize : public QDialog, public Ui::ClipStabilize_UI { Q_OBJECT public: explicit ClipStabilize(const std::vector &binIds, QString filterName, int out, QWidget *parent = nullptr); ~ClipStabilize() override; /** @brief Should the generated clip be added to current project. */ bool autoAddClip() const; /** @brief Return the filter parameters, filter name as value of "filter" entry. */ std::unordered_map filterParams() const; /** @brief Return the destination file or folder. */ QString destination() const; /** @brief Return the job description. */ QString desc() const; /* Return the name of the actual mlt filter used */ QString filterName() const; private slots: - void slotUpdateParams(); void slotValidate(); private: QString m_filtername; std::vector m_binIds; - QHash> m_ui_params; QVBoxLayout *m_vbox; - void fillParameters(QStringList); - std::unordered_map m_fixedParams; Timecode m_tc; + std::shared_ptrm_assetModel; signals: void addClip(const QUrl &url); }; #endif