File indexing completed on 2025-11-09 08:26:32

0001 /*
0002     SPDX-FileCopyrightText: 2017 Nicolas Carion
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "keyframemodel.hpp"
0007 #include "../../bpoint.h"
0008 #include "core.h"
0009 #include "doc/docundostack.hpp"
0010 #include "macros.hpp"
0011 #include "profiles/profilemodel.hpp"
0012 #include "rotoscoping/rotohelper.hpp"
0013 #include "utils/qcolorutils.h"
0014 
0015 #include <QDebug>
0016 #include <QJsonDocument>
0017 #include <QLineF>
0018 #include <QSize>
0019 #include <mlt++/Mlt.h>
0020 #include <utility>
0021 
0022 KeyframeModel::KeyframeModel(std::weak_ptr<AssetParameterModel> model, const QModelIndex &index, std::weak_ptr<DocUndoStack> undo_stack, int in, int out,
0023                              QObject *parent)
0024     : QAbstractListModel(parent)
0025     , m_model(std::move(model))
0026     , m_undoStack(std::move(undo_stack))
0027     , m_index(index)
0028     , m_lastData()
0029     , m_lock(QReadWriteLock::Recursive)
0030 {
0031     qDebug() << "Construct keyframemodel. Checking model:" << m_model.expired();
0032     if (auto ptr = m_model.lock()) {
0033         m_paramType = ptr->data(m_index, AssetParameterModel::TypeRole).value<ParamType>();
0034     }
0035     setup();
0036     refresh(in, out);
0037 }
0038 
0039 void KeyframeModel::setup()
0040 {
0041     // We connect the signals of the abstractitemmodel to a more generic one.
0042     connect(this, &KeyframeModel::columnsMoved, this, &KeyframeModel::modelChanged);
0043     connect(this, &KeyframeModel::columnsRemoved, this, &KeyframeModel::modelChanged);
0044     connect(this, &KeyframeModel::columnsInserted, this, &KeyframeModel::modelChanged);
0045     connect(this, &KeyframeModel::rowsMoved, this, &KeyframeModel::modelChanged);
0046     connect(this, &KeyframeModel::rowsRemoved, this, &KeyframeModel::modelChanged);
0047     connect(this, &KeyframeModel::rowsInserted, this, &KeyframeModel::modelChanged);
0048     connect(this, &KeyframeModel::modelReset, this, &KeyframeModel::modelChanged);
0049     connect(this, &KeyframeModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, const QVector<int> &roles) {
0050         QVector<int> notParamRoles = {SelectedRole, ActiveRole};
0051         if (roles.size() == 1 && notParamRoles.contains(roles.first())) {
0052             // Selection role changed, no need to update the keyframe parameters
0053             return;
0054         }
0055         Q_EMIT modelChanged();
0056     });
0057     connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
0058 }
0059 
0060 bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value, bool notify, Fun &undo, Fun &redo)
0061 {
0062     qDebug() << "ADD keyframe" << pos.frames(pCore->getCurrentFps()) << value << notify;
0063     QWriteLocker locker(&m_lock);
0064     Fun local_undo = []() { return true; };
0065     Fun local_redo = []() { return true; };
0066     if (m_keyframeList.count(pos) > 0) {
0067         qDebug() << "already there";
0068         if (std::pair<KeyframeType, QVariant>({type, value}) == m_keyframeList.at(pos)) {
0069             qDebug() << "nothing to do";
0070             return true; // nothing to do
0071         }
0072         // In this case we simply change the type and value
0073         KeyframeType oldType = m_keyframeList[pos].first;
0074         QVariant oldValue = m_keyframeList[pos].second;
0075         local_undo = updateKeyframe_lambda(pos, oldType, oldValue, notify);
0076         local_redo = updateKeyframe_lambda(pos, type, value, notify);
0077         if (local_redo()) {
0078             UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0079             return true;
0080         }
0081     } else {
0082         Fun redo_first = addKeyframe_lambda(pos, type, value, notify);
0083         if (redo_first()) {
0084             local_redo = addKeyframe_lambda(pos, type, value, true);
0085             local_undo = deleteKeyframe_lambda(pos, true);
0086             UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0087             return true;
0088         }
0089     }
0090     return false;
0091 }
0092 
0093 bool KeyframeModel::addKeyframe(int frame, double normalizedValue)
0094 {
0095     QVariant result = getNormalizedValue(normalizedValue);
0096     if (result.isValid()) {
0097         // TODO: Use default configurable kf type
0098         return addKeyframe(GenTime(frame, pCore->getCurrentFps()), KeyframeType::Linear, result);
0099     }
0100     return false;
0101 }
0102 
0103 bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value)
0104 {
0105     QWriteLocker locker(&m_lock);
0106     Fun undo = []() { return true; };
0107     Fun redo = []() { return true; };
0108 
0109     bool update = (m_keyframeList.count(pos) > 0);
0110     bool res = addKeyframe(pos, type, std::move(value), true, undo, redo);
0111     if (res) {
0112         PUSH_UNDO(undo, redo, update ? i18n("Change keyframe type") : i18n("Add keyframe"));
0113     }
0114     return res;
0115 }
0116 
0117 bool KeyframeModel::removeKeyframe(GenTime pos, Fun &undo, Fun &redo, bool notify, bool updateSelection)
0118 {
0119     qDebug() << "Going to remove keyframe at " << pos.frames(pCore->getCurrentFps()) << " NOTIFY: " << notify;
0120     qDebug() << "before" << getAnimProperty();
0121     QWriteLocker locker(&m_lock);
0122     Q_ASSERT(m_keyframeList.count(pos) > 0);
0123     KeyframeType oldType = m_keyframeList[pos].first;
0124     QVariant oldValue = m_keyframeList[pos].second;
0125     Fun select_undo = []() { return true; };
0126     Fun select_redo = []() { return true; };
0127     if (updateSelection) {
0128         if (auto ptr = m_model.lock()) {
0129             if (!ptr->m_selectedKeyframes.isEmpty()) {
0130                 int ix = getIndexForPos(pos);
0131                 QVector<int> selection;
0132                 QVector<int> prevSelection = ptr->m_selectedKeyframes;
0133                 for (auto &kf : prevSelection) {
0134                     if (kf == ix) {
0135                         continue;
0136                     }
0137                     if (kf < ix) {
0138                         selection << kf;
0139                     } else {
0140                         selection << (kf - 1);
0141                     }
0142                 }
0143                 setActiveKeyframe(-1);
0144                 std::sort(selection.begin(), selection.end());
0145                 select_redo = [this, selection]() {
0146                     setSelectedKeyframes(selection);
0147                     return true;
0148                 };
0149                 select_undo = [this, prevSelection]() {
0150                     setSelectedKeyframes(prevSelection);
0151                     return true;
0152                 };
0153             }
0154         }
0155     }
0156     Fun redo_first = deleteKeyframe_lambda(pos, notify);
0157     if (redo_first()) {
0158         Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue, notify);
0159         select_redo();
0160         qDebug() << "after" << getAnimProperty();
0161         UPDATE_UNDO_REDO(redo_first, local_undo, undo, redo);
0162         UPDATE_UNDO_REDO(select_redo, select_undo, undo, redo);
0163         return true;
0164     }
0165     return false;
0166 }
0167 
0168 bool KeyframeModel::duplicateKeyframe(GenTime srcPos, GenTime dstPos, Fun &undo, Fun &redo)
0169 {
0170     QWriteLocker locker(&m_lock);
0171     Q_ASSERT(m_keyframeList.count(srcPos) > 0);
0172     KeyframeType oldType = m_keyframeList[srcPos].first;
0173     QVariant oldValue = m_keyframeList[srcPos].second;
0174     Fun local_redo = addKeyframe_lambda(dstPos, oldType, oldValue, true);
0175     Fun local_undo = deleteKeyframe_lambda(dstPos, true);
0176     if (local_redo()) {
0177         UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0178         return true;
0179     }
0180     return false;
0181 }
0182 
0183 bool KeyframeModel::removeKeyframe(int frame)
0184 {
0185     GenTime pos(frame, pCore->getCurrentFps());
0186     return removeKeyframe(pos);
0187 }
0188 
0189 bool KeyframeModel::removeKeyframe(GenTime pos)
0190 {
0191     QWriteLocker locker(&m_lock);
0192     Fun undo = []() { return true; };
0193     Fun redo = []() { return true; };
0194 
0195     if (m_keyframeList.count(pos) > 0 && m_keyframeList.find(pos) == m_keyframeList.begin()) {
0196         return false; // initial point must stay
0197     }
0198 
0199     bool res = removeKeyframe(pos, undo, redo);
0200     if (res) {
0201         PUSH_UNDO(undo, redo, i18n("Delete keyframe"));
0202     }
0203     return res;
0204 }
0205 
0206 GenTime KeyframeModel::getPosAtIndex(int ix) const
0207 {
0208     QList<GenTime> positions = getKeyframePos();
0209     std::sort(positions.begin(), positions.end());
0210     if (ix < 0 || ix >= positions.count()) {
0211         return GenTime();
0212     }
0213     return positions.at(ix);
0214 }
0215 
0216 bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, const QVariant &newVal, Fun &undo, Fun &redo, bool updateView)
0217 {
0218     qDebug() << "starting to move keyframe" << oldPos.frames(pCore->getCurrentFps()) << pos.frames(pCore->getCurrentFps());
0219     QWriteLocker locker(&m_lock);
0220     // Check if we have several selected keyframes
0221     if (oldPos == pos) {
0222         if (!newVal.isValid()) {
0223             // no change
0224             return true;
0225         }
0226     }
0227     if (auto ptr = m_model.lock()) {
0228         if (ptr->m_selectedKeyframes.size() > 1) {
0229             // We have several selected keyframes, move them all
0230             double offset = 0.;
0231 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0232             if (newVal.isValid() && newVal.type() == QVariant::Double) {
0233 #else
0234             if (newVal.isValid() && newVal.typeId() == QMetaType::Double) {
0235 #endif
0236                 int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(oldPos)));
0237                 double oldVal = data(index(row), NormalizedValueRole).toDouble();
0238                 offset = newVal.toDouble() - oldVal;
0239             }
0240             QVector<GenTime> positions;
0241             for (auto &kf : ptr->m_selectedKeyframes) {
0242                 if (kf > 0) {
0243                     positions << getPosAtIndex(kf);
0244                 }
0245             }
0246             GenTime delta = pos - oldPos;
0247             if (pos > oldPos) {
0248                 // Moving right, reverse sort
0249                 std::sort(positions.rbegin(), positions.rend());
0250                 // Check max pos
0251                 bool ok = false;
0252                 GenTime test = positions.first();
0253                 auto next = getNextKeyframe(test, &ok);
0254                 if (ok) {
0255                     delta = qMin(delta, next.first - GenTime(1, pCore->getCurrentFps()) - test);
0256                 }
0257             } else {
0258                 // Moving left
0259                 std::sort(positions.begin(), positions.end());
0260                 // Check min pos
0261                 bool ok = false;
0262                 GenTime test = positions.first();
0263                 auto next = getPrevKeyframe(test, &ok);
0264                 if (ok) {
0265                     delta = qMax(delta, (next.first + GenTime(1, pCore->getCurrentFps())) - test);
0266                 }
0267             }
0268             if (delta == GenTime()) {
0269                 if (!newVal.isValid()) {
0270                     // no change
0271                     return true;
0272                 }
0273             }
0274             bool res = true;
0275             for (auto &p : positions) {
0276                 if (p == oldPos) {
0277                     res = res && moveOneKeyframe(oldPos, oldPos + delta, newVal, undo, redo, updateView);
0278                 } else {
0279                     if (!qFuzzyIsNull(offset)) {
0280                         // Calculate new value
0281                         int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(p)));
0282                         double newVal2 = qBound(0., data(index(row), NormalizedValueRole).toDouble() + offset, 1.);
0283                         res = res && moveOneKeyframe(p, p + delta, newVal2, undo, redo, updateView);
0284                     } else {
0285                         res = res && moveOneKeyframe(p, p + delta, QVariant(), undo, redo, updateView);
0286                     }
0287                 }
0288             }
0289             return res;
0290         } else {
0291             // We have only one selected keyframe
0292             if (pos > oldPos) {
0293                 // Moving right
0294                 bool ok = false;
0295                 auto next = getNextKeyframe(oldPos, &ok);
0296                 if (ok) {
0297                     pos = qMin(pos, next.first - GenTime(1, pCore->getCurrentFps()));
0298                 }
0299             } else {
0300                 // Moving left
0301                 bool ok = false;
0302                 auto next = getPrevKeyframe(oldPos, &ok);
0303                 if (ok) {
0304                     pos = qMax(pos, next.first + GenTime(1, pCore->getCurrentFps()));
0305                 }
0306             }
0307             return moveOneKeyframe(oldPos, pos, newVal, undo, redo, updateView);
0308         }
0309     }
0310     return false;
0311 }
0312 
0313 bool KeyframeModel::moveOneKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, Fun &undo, Fun &redo, bool updateView)
0314 {
0315     qDebug() << "starting to move keyframe" << oldPos.frames(pCore->getCurrentFps()) << pos.frames(pCore->getCurrentFps());
0316     QWriteLocker locker(&m_lock);
0317     Q_ASSERT(m_keyframeList.count(oldPos) > 0);
0318     if (oldPos == pos) {
0319         if (!newVal.isValid()) {
0320             // no change
0321             return true;
0322         }
0323         if (m_paramType == ParamType::AnimatedRect) {
0324             return updateKeyframe(pos, newVal);
0325         }
0326         // Calculate real value from normalized
0327         QVariant result = getNormalizedValue(newVal.toDouble());
0328         return updateKeyframe(pos, result);
0329     }
0330     if (oldPos != pos && hasKeyframe(pos)) {
0331         // Move rejected, another keyframe is here
0332         qDebug() << "==== MOVE REJECTED!!";
0333         return false;
0334     }
0335     KeyframeType oldType = m_keyframeList[oldPos].first;
0336     QVariant oldValue = m_keyframeList[oldPos].second;
0337     Fun local_undo = []() { return true; };
0338     Fun local_redo = []() { return true; };
0339     qDebug() << getAnimProperty();
0340     // TODO: use the new Animation::key_set_frame to move a keyframe
0341     bool res = removeKeyframe(oldPos, local_undo, local_redo, true, false);
0342     qDebug() << "Move keyframe finished deletion:" << res;
0343     qDebug() << getAnimProperty();
0344     if (res) {
0345         if (m_paramType == ParamType::AnimatedRect) {
0346             if (!newVal.isValid()) {
0347                 newVal = oldValue;
0348             }
0349             res = addKeyframe(pos, oldType, newVal, updateView, local_undo, local_redo);
0350         } else if (newVal.isValid()) {
0351             QVariant result = getNormalizedValue(newVal.toDouble());
0352             if (result.isValid()) {
0353                 res = addKeyframe(pos, oldType, result, updateView, local_undo, local_redo);
0354             }
0355         } else {
0356             res = addKeyframe(pos, oldType, oldValue, updateView, local_undo, local_redo);
0357         }
0358         qDebug() << "Move keyframe finished insertion:" << res;
0359         qDebug() << getAnimProperty();
0360     }
0361     if (res) {
0362         UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0363     } else {
0364         bool undone = local_undo();
0365         Q_ASSERT(undone);
0366     }
0367     return res;
0368 }
0369 
0370 bool KeyframeModel::moveKeyframe(int oldPos, int pos, bool logUndo)
0371 {
0372     GenTime oPos(oldPos, pCore->getCurrentFps());
0373     GenTime nPos(pos, pCore->getCurrentFps());
0374     return moveKeyframe(oPos, nPos, QVariant(), logUndo);
0375 }
0376 
0377 bool KeyframeModel::offsetKeyframes(int oldPos, int pos, bool logUndo)
0378 {
0379     if (oldPos == pos) return true;
0380     GenTime oldFrame(oldPos, pCore->getCurrentFps());
0381     Q_ASSERT(m_keyframeList.count(oldFrame) > 0);
0382     GenTime diff(pos - oldPos, pCore->getCurrentFps());
0383     QWriteLocker locker(&m_lock);
0384     Fun undo = []() { return true; };
0385     Fun redo = []() { return true; };
0386     QList<GenTime> times;
0387     for (const auto &m : m_keyframeList) {
0388         if (m.first < oldFrame) continue;
0389         times << m.first;
0390     }
0391     bool res = true;
0392     for (const auto &t : qAsConst(times)) {
0393         res &= moveKeyframe(t, t + diff, QVariant(), undo, redo);
0394     }
0395     if (res && logUndo) {
0396         PUSH_UNDO(undo, redo, i18nc("@action", "Move keyframes"));
0397     }
0398     return res;
0399 }
0400 
0401 bool KeyframeModel::moveKeyframe(int oldPos, int pos, QVariant newVal)
0402 {
0403     GenTime oPos(oldPos, pCore->getCurrentFps());
0404     GenTime nPos(pos, pCore->getCurrentFps());
0405     return moveKeyframe(oPos, nPos, std::move(newVal), true);
0406 }
0407 
0408 bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, bool logUndo)
0409 {
0410     QWriteLocker locker(&m_lock);
0411     Q_ASSERT(m_keyframeList.count(oldPos) > 0);
0412     if (oldPos == pos) return true;
0413     Fun undo = []() { return true; };
0414     Fun redo = []() { return true; };
0415     bool res = moveKeyframe(oldPos, pos, std::move(newVal), undo, redo);
0416     if (res && logUndo) {
0417         PUSH_UNDO(undo, redo, i18nc("@action", "Move keyframe"));
0418     }
0419     return res;
0420 }
0421 
0422 bool KeyframeModel::directUpdateKeyframe(GenTime pos, QVariant value, bool notify)
0423 {
0424     QWriteLocker locker(&m_lock);
0425     Q_ASSERT(m_keyframeList.count(pos) > 0);
0426     KeyframeType type = m_keyframeList[pos].first;
0427     auto operation = updateKeyframe_lambda(pos, type, std::move(value), notify);
0428     return operation();
0429 }
0430 
0431 bool KeyframeModel::updateKeyframe(GenTime pos, const QVariant &value, Fun &undo, Fun &redo, bool update)
0432 {
0433     QWriteLocker locker(&m_lock);
0434     Q_ASSERT(m_keyframeList.count(pos) > 0);
0435     KeyframeType type = m_keyframeList[pos].first;
0436     QVariant oldValue = m_keyframeList[pos].second;
0437     // Check if keyframe is different
0438     if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::ColorWheel) {
0439         if (qFuzzyCompare(oldValue.toDouble(), value.toDouble())) return true;
0440     }
0441     auto operation = updateKeyframe_lambda(pos, type, value, update);
0442     auto reverse = updateKeyframe_lambda(pos, type, oldValue, update);
0443     bool res = operation();
0444     if (res) {
0445         UPDATE_UNDO_REDO(operation, reverse, undo, redo);
0446     }
0447     return res;
0448 }
0449 
0450 bool KeyframeModel::updateKeyframe(int pos, double newVal)
0451 {
0452     GenTime Pos(pos, pCore->getCurrentFps());
0453     if (auto ptr = m_model.lock()) {
0454         double min = ptr->data(m_index, AssetParameterModel::VisualMinRole).toDouble();
0455         double max = ptr->data(m_index, AssetParameterModel::VisualMaxRole).toDouble();
0456         if (qFuzzyIsNull(min) && qFuzzyIsNull(max)) {
0457             min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
0458             max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
0459         }
0460         double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
0461         double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
0462         int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
0463         double realValue;
0464         if (logRole == -1) {
0465             // Logarythmic scale
0466             if (newVal >= 0.5) {
0467                 realValue = norm + pow(2 * (newVal - 0.5), 10.0 / 6) * (max / factor - norm);
0468             } else {
0469                 realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
0470             }
0471         } else {
0472             realValue = (newVal * (max - min) + min) / factor;
0473         }
0474         return updateKeyframe(Pos, realValue);
0475     }
0476     return false;
0477 }
0478 
0479 bool KeyframeModel::updateKeyframe(GenTime pos, QVariant value)
0480 {
0481     QWriteLocker locker(&m_lock);
0482     Q_ASSERT(m_keyframeList.count(pos) > 0);
0483 
0484     Fun undo = []() { return true; };
0485     Fun redo = []() { return true; };
0486     bool res = updateKeyframe(pos, value, undo, redo);
0487     if (res) {
0488         PUSH_UNDO(undo, redo, i18n("Update keyframe"));
0489     }
0490     return res;
0491 }
0492 
0493 KeyframeType convertFromMltType(mlt_keyframe_type type)
0494 {
0495     switch (type) {
0496     case mlt_keyframe_linear:
0497         return KeyframeType::Linear;
0498     case mlt_keyframe_discrete:
0499         return KeyframeType::Discrete;
0500 #ifdef USE_MLT_NEW_KEYFRAMES
0501     case mlt_keyframe_smooth_natural:
0502         return KeyframeType::CurveSmooth;
0503     case mlt_keyframe_bounce_in:
0504         return KeyframeType::BounceIn;
0505     case mlt_keyframe_bounce_out:
0506         return KeyframeType::BounceOut;
0507     case mlt_keyframe_cubic_in:
0508         return KeyframeType::CubicIn;
0509     case mlt_keyframe_cubic_out:
0510         return KeyframeType::CubicOut;
0511     case mlt_keyframe_exponential_in:
0512         return KeyframeType::ExponentialIn;
0513     case mlt_keyframe_exponential_out:
0514         return KeyframeType::ExponentialOut;
0515     case mlt_keyframe_circular_in:
0516         return KeyframeType::CircularIn;
0517     case mlt_keyframe_circular_out:
0518         return KeyframeType::CircularOut;
0519     case mlt_keyframe_elastic_in:
0520         return KeyframeType::ElasticIn;
0521     case mlt_keyframe_elastic_out:
0522         return KeyframeType::ElasticOut;
0523 #endif
0524     case mlt_keyframe_smooth:
0525         return KeyframeType::Curve;
0526     default:
0527         return KeyframeType::Linear;
0528     }
0529 }
0530 
0531 bool KeyframeModel::updateKeyframeType(GenTime pos, int type, Fun &undo, Fun &redo)
0532 {
0533     QWriteLocker locker(&m_lock);
0534     Q_ASSERT(m_keyframeList.count(pos) > 0);
0535     KeyframeType oldType = m_keyframeList[pos].first;
0536     KeyframeType newType = convertFromMltType(mlt_keyframe_type(type));
0537     QVariant value = m_keyframeList[pos].second;
0538     // Check if keyframe is different
0539     if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::ColorWheel) {
0540         if (oldType == newType) return true;
0541     }
0542     auto operation = updateKeyframe_lambda(pos, newType, value, true);
0543     auto reverse = updateKeyframe_lambda(pos, oldType, value, true);
0544     bool res = operation();
0545     if (res) {
0546         UPDATE_UNDO_REDO(operation, reverse, undo, redo);
0547     }
0548     return res;
0549 }
0550 
0551 Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, const QVariant &value, bool notify)
0552 {
0553     QWriteLocker locker(&m_lock);
0554     return [this, pos, type, value, notify]() {
0555         // qDebug() << "update lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
0556         Q_ASSERT(m_keyframeList.count(pos) > 0);
0557         int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
0558         m_keyframeList[pos].first = type;
0559         m_keyframeList[pos].second = value;
0560         if (notify) Q_EMIT dataChanged(index(row), index(row), {ValueRole, NormalizedValueRole, TypeRole});
0561         return true;
0562     };
0563 }
0564 
0565 Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type, const QVariant &value, bool notify)
0566 {
0567     QWriteLocker locker(&m_lock);
0568     return [this, notify, pos, type, value]() {
0569         qDebug() << "add lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
0570         Q_ASSERT(m_keyframeList.count(pos) == 0);
0571         // We determine the row of the newly added marker
0572         auto insertionIt = m_keyframeList.lower_bound(pos);
0573         int insertionRow = static_cast<int>(m_keyframeList.size());
0574         if (insertionIt != m_keyframeList.end()) {
0575             insertionRow = static_cast<int>(std::distance(m_keyframeList.begin(), insertionIt));
0576         }
0577         if (notify) beginInsertRows(QModelIndex(), insertionRow, insertionRow);
0578         m_keyframeList[pos].first = type;
0579         m_keyframeList[pos].second = value;
0580         if (notify) endInsertRows();
0581         return true;
0582     };
0583 }
0584 
0585 Fun KeyframeModel::deleteKeyframe_lambda(GenTime pos, bool notify)
0586 {
0587     QWriteLocker locker(&m_lock);
0588     return [this, pos, notify]() {
0589         qDebug() << "delete lambda" << pos.frames(pCore->getCurrentFps()) << notify;
0590         qDebug() << "before" << getAnimProperty();
0591         Q_ASSERT(m_keyframeList.count(pos) > 0);
0592         // Q_ASSERT(pos != GenTime()); // cannot delete initial point
0593         int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
0594         if (notify) beginRemoveRows(QModelIndex(), row, row);
0595         m_keyframeList.erase(pos);
0596         if (notify) endRemoveRows();
0597         qDebug() << "after" << getAnimProperty();
0598         return true;
0599     };
0600 }
0601 
0602 QHash<int, QByteArray> KeyframeModel::roleNames() const
0603 {
0604     QHash<int, QByteArray> roles;
0605     roles[PosRole] = "position";
0606     roles[FrameRole] = "frame";
0607     roles[TypeRole] = "type";
0608     roles[ValueRole] = "value";
0609     roles[SelectedRole] = "selected";
0610     roles[ActiveRole] = "active";
0611     roles[NormalizedValueRole] = "normalizedValue";
0612     roles[MoveOnlyRole] = "moveOnly";
0613     return roles;
0614 }
0615 
0616 QVariant KeyframeModel::data(const QModelIndex &index, int role) const
0617 {
0618     READ_LOCK();
0619     if (index.row() < 0 || index.row() >= static_cast<int>(m_keyframeList.size()) || !index.isValid()) {
0620         return QVariant();
0621     }
0622     auto it = m_keyframeList.begin();
0623     std::advance(it, index.row());
0624     switch (role) {
0625     case Qt::DisplayRole:
0626     case Qt::EditRole:
0627     case ValueRole:
0628         if (m_paramType == ParamType::Roto_spline) {
0629             return 0;
0630         }
0631         return it->second.second;
0632     case MoveOnlyRole: {
0633         if (m_paramType == ParamType::Roto_spline) {
0634             return true;
0635         }
0636         return false;
0637     }
0638     case NormalizedValueRole: {
0639         if (m_paramType == ParamType::AnimatedRect) {
0640             const QString &data = it->second.second.toString();
0641             bool ok;
0642             double converted = data.section(QLatin1Char(' '), -1).toDouble(&ok);
0643             if (!ok) {
0644                 qDebug() << "QLocale: Could not convert animated rect opacity" << data;
0645             }
0646             return converted;
0647         }
0648         if (m_paramType == ParamType::Roto_spline) {
0649             return 0.5;
0650         }
0651         double val = it->second.second.toDouble();
0652         if (auto ptr = m_model.lock()) {
0653             Q_ASSERT(m_index.isValid());
0654             double min = ptr->data(m_index, AssetParameterModel::VisualMinRole).toDouble();
0655             double max = ptr->data(m_index, AssetParameterModel::VisualMaxRole).toDouble();
0656             if (qFuzzyIsNull(min) && qFuzzyIsNull(max)) {
0657                 min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
0658                 max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
0659             }
0660             double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
0661             double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
0662             int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
0663             double linear = val * factor;
0664             if (logRole == -1) {
0665                 // Logarythmic scale
0666                 // transform current value to 0..1 scale
0667                 if (linear >= norm) {
0668                     double scaled = (linear - norm) / (max * factor - norm);
0669                     return 0.5 + pow(scaled, 0.6) * 0.5;
0670                 }
0671                 double scaled = (linear - norm) / (min * factor - norm);
0672                 // Log scale
0673                 return 0.5 - pow(scaled, 0.6) * 0.5;
0674             }
0675             return (linear - min) / (max - min);
0676         } else {
0677             qDebug() << "// CANNOT LOCK effect MODEL";
0678         }
0679         return 1;
0680     }
0681     case PosRole:
0682         return it->first.seconds();
0683     case FrameRole:
0684     case Qt::UserRole:
0685         return it->first.frames(pCore->getCurrentFps());
0686     case TypeRole:
0687         return QVariant::fromValue<KeyframeType>(it->second.first);
0688     case SelectedRole:
0689         if (auto ptr = m_model.lock()) {
0690             return ptr->m_selectedKeyframes.contains(index.row());
0691         }
0692         break;
0693     case ActiveRole:
0694         if (auto ptr = m_model.lock()) {
0695             return ptr->m_activeKeyframe == index.row();
0696         }
0697         break;
0698     }
0699     return QVariant();
0700 }
0701 
0702 int KeyframeModel::rowCount(const QModelIndex &parent) const
0703 {
0704     READ_LOCK();
0705     if (parent.isValid()) return 0;
0706     return static_cast<int>(m_keyframeList.size());
0707 }
0708 
0709 bool KeyframeModel::singleKeyframe() const
0710 {
0711     READ_LOCK();
0712     return m_keyframeList.size() <= 1;
0713 }
0714 
0715 Keyframe KeyframeModel::getKeyframe(const GenTime &pos, bool *ok) const
0716 {
0717     READ_LOCK();
0718     if (m_keyframeList.count(pos) == 0) {
0719         // return empty marker
0720         *ok = false;
0721         return {GenTime(), KeyframeType::Linear};
0722     }
0723     *ok = true;
0724     return {pos, m_keyframeList.at(pos).first};
0725 }
0726 
0727 Keyframe KeyframeModel::getNextKeyframe(const GenTime &pos, bool *ok) const
0728 {
0729     auto it = m_keyframeList.upper_bound(pos);
0730     if (it == m_keyframeList.end()) {
0731         // return empty marker
0732         *ok = false;
0733         return {GenTime(), KeyframeType::Linear};
0734     }
0735     *ok = true;
0736     return {(*it).first, (*it).second.first};
0737 }
0738 
0739 Keyframe KeyframeModel::getPrevKeyframe(const GenTime &pos, bool *ok) const
0740 {
0741     auto it = m_keyframeList.lower_bound(pos);
0742     if (it == m_keyframeList.begin()) {
0743         // return empty marker
0744         *ok = false;
0745         return {GenTime(), KeyframeType::Linear};
0746     }
0747     --it;
0748     *ok = true;
0749     return {(*it).first, (*it).second.first};
0750 }
0751 
0752 Keyframe KeyframeModel::getClosestKeyframe(const GenTime &pos, bool *ok) const
0753 {
0754     if (m_keyframeList.count(pos) > 0) {
0755         return getKeyframe(pos, ok);
0756     }
0757     bool ok1, ok2;
0758     auto next = getNextKeyframe(pos, &ok1);
0759     auto prev = getPrevKeyframe(pos, &ok2);
0760     *ok = ok1 || ok2;
0761     if (ok1 && ok2) {
0762         double fps = pCore->getCurrentFps();
0763         if (qAbs(next.first.frames(fps) - pos.frames(fps)) < qAbs(prev.first.frames(fps) - pos.frames(fps))) {
0764             return next;
0765         }
0766         return prev;
0767     } else if (ok1) {
0768         return next;
0769     } else if (ok2) {
0770         return prev;
0771     }
0772     // return empty marker
0773     return {GenTime(), KeyframeType::Linear};
0774 }
0775 
0776 bool KeyframeModel::hasKeyframe(int frame) const
0777 {
0778     return hasKeyframe(GenTime(frame, pCore->getCurrentFps()));
0779 }
0780 bool KeyframeModel::hasKeyframe(const GenTime &pos) const
0781 {
0782     READ_LOCK();
0783     return m_keyframeList.count(pos) > 0;
0784 }
0785 
0786 bool KeyframeModel::removeAllKeyframes(Fun &undo, Fun &redo)
0787 {
0788     QWriteLocker locker(&m_lock);
0789     Fun local_undo = []() { return true; };
0790     Fun local_redo = []() { return true; };
0791     int kfrCount = int(m_keyframeList.size()) - 1;
0792     // Clear selection
0793     if (auto ptr = m_model.lock()) {
0794         ptr->m_selectedKeyframes = {};
0795     }
0796     if (kfrCount <= 0) {
0797         // Nothing to do
0798         UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0799         return true;
0800     }
0801     // we trigger only one global remove/insertrow event
0802     Fun update_redo_start = [this, kfrCount]() {
0803         beginRemoveRows(QModelIndex(), 1, kfrCount);
0804         return true;
0805     };
0806     Fun update_redo_end = [this]() {
0807         endRemoveRows();
0808         return true;
0809     };
0810     Fun update_undo_start = [this, kfrCount]() {
0811         beginInsertRows(QModelIndex(), 1, kfrCount);
0812         return true;
0813     };
0814     Fun update_undo_end = [this]() {
0815         endInsertRows();
0816         return true;
0817     };
0818     PUSH_LAMBDA(update_redo_start, local_redo);
0819     PUSH_LAMBDA(update_undo_start, local_undo);
0820     QList<GenTime> all_pos = getKeyframePos();
0821     update_redo_start();
0822     bool res = true;
0823     bool first = true;
0824     for (const auto &p : qAsConst(all_pos)) {
0825         if (first) { // skip first point
0826             first = false;
0827             continue;
0828         }
0829         res = removeKeyframe(p, local_undo, local_redo, false);
0830         if (!res) {
0831             bool undone = local_undo();
0832             Q_ASSERT(undone);
0833             return false;
0834         }
0835     }
0836     update_redo_end();
0837     PUSH_LAMBDA(update_redo_end, local_redo);
0838     PUSH_LAMBDA(update_undo_end, local_undo);
0839     UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0840     return true;
0841 }
0842 
0843 bool KeyframeModel::removeAllKeyframes()
0844 {
0845     QWriteLocker locker(&m_lock);
0846     Fun undo = []() { return true; };
0847     Fun redo = []() { return true; };
0848     bool res = removeAllKeyframes(undo, redo);
0849     if (res) {
0850         PUSH_UNDO(undo, redo, i18n("Delete all keyframes"));
0851     }
0852     return res;
0853 }
0854 
0855 mlt_keyframe_type convertToMltType(KeyframeType type)
0856 {
0857     switch (type) {
0858     case KeyframeType::Linear:
0859         return mlt_keyframe_linear;
0860     case KeyframeType::Discrete:
0861         return mlt_keyframe_discrete;
0862 #ifdef USE_MLT_NEW_KEYFRAMES
0863     case KeyframeType::CurveSmooth:
0864         return mlt_keyframe_smooth_natural;
0865     case KeyframeType::BounceIn:
0866         return mlt_keyframe_bounce_in;
0867     case KeyframeType::BounceOut:
0868         return mlt_keyframe_bounce_out;
0869     case KeyframeType::CubicIn:
0870         return mlt_keyframe_cubic_in;
0871     case KeyframeType::CubicOut:
0872         return mlt_keyframe_cubic_out;
0873     case KeyframeType::ExponentialIn:
0874         return mlt_keyframe_exponential_in;
0875     case KeyframeType::ExponentialOut:
0876         return mlt_keyframe_exponential_out;
0877     case KeyframeType::CircularIn:
0878         return mlt_keyframe_circular_in;
0879     case KeyframeType::CircularOut:
0880         return mlt_keyframe_circular_out;
0881     case KeyframeType::ElasticIn:
0882         return mlt_keyframe_elastic_in;
0883     case KeyframeType::ElasticOut:
0884         return mlt_keyframe_elastic_out;
0885 #endif
0886     // Deprecated
0887     case KeyframeType::Curve:
0888         return mlt_keyframe_smooth;
0889     default:
0890         return mlt_keyframe_linear;
0891     }
0892 }
0893 
0894 QString KeyframeModel::getAnimProperty() const
0895 {
0896     if (m_paramType == ParamType::Roto_spline) {
0897         return getRotoProperty();
0898     }
0899     Mlt::Properties mlt_prop;
0900     if (auto ptr = m_model.lock()) {
0901         ptr->passProperties(mlt_prop);
0902     }
0903     int ix = 0;
0904     bool first = true;
0905     std::shared_ptr<Mlt::Animation> anim(nullptr);
0906     for (const auto &keyframe : m_keyframeList) {
0907         switch (m_paramType) {
0908         case ParamType::AnimatedRect:
0909         case ParamType::Color:
0910             mlt_prop.anim_set("key", keyframe.second.second.toString().toUtf8().constData(), keyframe.first.frames(pCore->getCurrentFps()));
0911             break;
0912         default:
0913             mlt_prop.anim_set("key", keyframe.second.second.toDouble(), keyframe.first.frames(pCore->getCurrentFps()));
0914             break;
0915         }
0916         if (first) {
0917             anim.reset(mlt_prop.get_anim("key"));
0918             first = false;
0919         }
0920         anim->key_set_type(ix, convertToMltType(keyframe.second.first));
0921         ix++;
0922     }
0923     QString ret;
0924     if (anim) {
0925         char *cut = anim->serialize_cut();
0926         ret = QString(cut);
0927         free(cut);
0928     }
0929     return ret;
0930 }
0931 
0932 QString KeyframeModel::getRotoProperty() const
0933 {
0934     QJsonDocument doc;
0935     if (auto ptr = m_model.lock()) {
0936         int in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
0937         int out = in + ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
0938         QVariantMap map;
0939         for (const auto &keyframe : m_keyframeList) {
0940             map.insert(QString::number(keyframe.first.frames(pCore->getCurrentFps())).rightJustified(int(log10(double(out))) + 1, '0'), keyframe.second.second);
0941         }
0942         doc = QJsonDocument::fromVariant(map);
0943     }
0944     return doc.toJson();
0945 }
0946 
0947 void KeyframeModel::parseAnimProperty(const QString &prop, int in, int out)
0948 {
0949     Fun undo = []() { return true; };
0950     Fun redo = []() { return true; };
0951     disconnect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
0952     removeAllKeyframes(undo, redo);
0953     bool useOpacity = true;
0954     Mlt::Properties mlt_prop;
0955     if (auto ptr = m_model.lock()) {
0956         if (out <= in) {
0957             in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
0958             out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
0959         }
0960         ptr->passProperties(mlt_prop);
0961         useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
0962     } else {
0963         qDebug() << "###################\n\n/// ERROR LOCKING MODEL!!! ";
0964     }
0965     mlt_prop.set("key", prop.toUtf8().constData());
0966     // This is a fake query to force the animation to be parsed
0967     (void)mlt_prop.anim_get_double("key", 0, out);
0968 
0969     Mlt::Animation anim = mlt_prop.get_animation("key");
0970 
0971     qDebug() << "Found" << anim.key_count() << ", OUT: " << out << ", animation properties: " << prop;
0972     bool useDefaultType = !prop.contains(QLatin1Char('='));
0973     for (int i = 0; i < anim.key_count(); ++i) {
0974         int frame;
0975         mlt_keyframe_type type;
0976         anim.key_get(i, frame, type);
0977         if (useDefaultType) {
0978             // TODO: use a default user defined type
0979             type = mlt_keyframe_linear;
0980         }
0981         QVariant value;
0982         switch (m_paramType) {
0983         case ParamType::AnimatedRect: {
0984             mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
0985             if (useOpacity) {
0986                 value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(rect.o, 0, 'f'));
0987             } else {
0988                 value = QVariant(QStringLiteral("%1 %2 %3 %4").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h));
0989             }
0990             break;
0991         }
0992         case ParamType::Color: {
0993             mlt_color mltColor = mlt_prop.anim_get_color("key", frame);
0994             QColor color(mltColor.r, mltColor.g, mltColor.b, mltColor.a);
0995             value = QVariant(QColorUtils::colorToString(color, true));
0996             break;
0997         }
0998 
0999         default:
1000             value = QVariant(mlt_prop.anim_get_double("key", frame));
1001             break;
1002         }
1003         if (i == 0 && frame > in) {
1004             // Always add a keyframe at start pos
1005             addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo);
1006         } else if (frame == in && hasKeyframe(GenTime(in))) {
1007             // First keyframe already exists, adjust its value
1008             updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, true);
1009             continue;
1010         }
1011         addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo);
1012     }
1013     connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
1014 }
1015 
1016 void KeyframeModel::resetAnimProperty(const QString &prop)
1017 {
1018     Fun undo = []() { return true; };
1019     Fun redo = []() { return true; };
1020 
1021     // Delete all existing keyframes
1022     QSignalBlocker bk(this);
1023     removeAllKeyframes(undo, redo);
1024 
1025     Mlt::Properties mlt_prop;
1026     int in = 0;
1027     bool useOpacity = true;
1028     if (auto ptr = m_model.lock()) {
1029         in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
1030         ptr->passProperties(mlt_prop);
1031         if (m_paramType == ParamType::AnimatedRect) {
1032             useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
1033         }
1034     }
1035     mlt_prop.set("key", prop.toUtf8().constData());
1036     // This is a fake query to force the animation to be parsed
1037     (void)mlt_prop.anim_get_int("key", 0, 0);
1038 
1039     Mlt::Animation anim = mlt_prop.get_animation("key");
1040 
1041     qDebug() << "Found" << anim.key_count() << "animation properties";
1042     for (int i = 0; i < anim.key_count(); ++i) {
1043         int frame;
1044         mlt_keyframe_type type;
1045         anim.key_get(i, frame, type);
1046         if (!prop.contains(QLatin1Char('='))) {
1047             // TODO: use a default user defined type
1048             type = mlt_keyframe_linear;
1049         }
1050         QVariant value;
1051         switch (m_paramType) {
1052         case ParamType::AnimatedRect: {
1053             mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
1054             if (useOpacity) {
1055                 value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(QString::number(rect.o, 'f')));
1056             } else {
1057                 value = QVariant(QStringLiteral("%1 %2 %3 %4").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h));
1058             }
1059             break;
1060         }
1061         default:
1062             value = QVariant(mlt_prop.anim_get_double("key", frame));
1063             break;
1064         }
1065         if (i == 0 && frame > in) {
1066             // Always add a keyframe at start pos
1067             addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo);
1068         } else if (frame == in && hasKeyframe(GenTime(in))) {
1069             // First keyframe already exists, adjust its value
1070             updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, false);
1071             continue;
1072         }
1073         addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo);
1074     }
1075     QString effectName;
1076     if (auto ptr = m_model.lock()) {
1077         effectName = ptr->data(m_index, Qt::DisplayRole).toString();
1078     } else {
1079         effectName = i18n("effect");
1080     }
1081     Fun update_local = [this]() {
1082         Q_EMIT dataChanged(index(0), index(int(m_keyframeList.size())), {});
1083         return true;
1084     };
1085     update_local();
1086     PUSH_LAMBDA(update_local, undo);
1087     PUSH_LAMBDA(update_local, redo);
1088     PUSH_UNDO(undo, redo, i18n("Reset %1", effectName));
1089 }
1090 
1091 void KeyframeModel::parseRotoProperty(const QString &prop)
1092 {
1093     Fun undo = []() { return true; };
1094     Fun redo = []() { return true; };
1095 
1096     QJsonParseError jsonError;
1097     QJsonDocument doc = QJsonDocument::fromJson(prop.toUtf8(), &jsonError);
1098     QVariant data = doc.toVariant();
1099     if (data.canConvert<QVariantMap>()) {
1100         QMap<QString, QVariant> map = data.toMap();
1101         QMap<QString, QVariant>::const_iterator i = map.constBegin();
1102         while (i != map.constEnd()) {
1103             addKeyframe(GenTime(i.key().toInt(), pCore->getCurrentFps()), KeyframeType::Linear, i.value(), false, undo, redo);
1104             ++i;
1105         }
1106     }
1107 }
1108 
1109 QVariant KeyframeModel::getInterpolatedValue(int p) const
1110 {
1111     auto pos = GenTime(p, pCore->getCurrentFps());
1112     return getInterpolatedValue(pos);
1113 }
1114 
1115 QVariant KeyframeModel::updateInterpolated(const QVariant &interpValue, double val)
1116 {
1117     QStringList vals = interpValue.toString().split(QLatin1Char(' '));
1118     if (!vals.isEmpty()) {
1119         vals[vals.size() - 1] = QString::number(val, 'f');
1120     }
1121     return vals.join(QLatin1Char(' '));
1122 }
1123 
1124 QVariant KeyframeModel::getNormalizedValue(double newVal) const
1125 {
1126     if (auto ptr = m_model.lock()) {
1127         double min = ptr->data(m_index, AssetParameterModel::VisualMinRole).toDouble();
1128         double max = ptr->data(m_index, AssetParameterModel::VisualMaxRole).toDouble();
1129         if (qFuzzyIsNull(min) && qFuzzyIsNull(max)) {
1130             min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
1131             max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
1132         }
1133         if (qFuzzyIsNull(min) && qFuzzyIsNull(max)) {
1134             min = 0.;
1135             max = 1.;
1136         }
1137         double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
1138         double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
1139         int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
1140         double realValue;
1141         if (logRole == -1) {
1142             // Logarythmic scale
1143             if (newVal >= 0.5) {
1144                 realValue = norm + pow(2 * (newVal - 0.5), 10.0 / 6) * (max / factor - norm);
1145             } else {
1146                 realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
1147             }
1148         } else {
1149             realValue = (newVal * (max - min) + min) / factor;
1150         }
1151         return QVariant(realValue);
1152     }
1153     return QVariant();
1154 }
1155 
1156 QVariant KeyframeModel::getInterpolatedValue(const GenTime &pos) const
1157 {
1158     if (m_keyframeList.count(pos) > 0) {
1159         return m_keyframeList.at(pos).second;
1160     }
1161     if (m_keyframeList.size() == 0) {
1162         return QVariant();
1163     }
1164     Mlt::Properties mlt_prop;
1165     QString animData;
1166     int out = 0;
1167     bool useOpacity = false;
1168     if (auto ptr = m_model.lock()) {
1169         ptr->passProperties(mlt_prop);
1170         out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
1171         useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
1172         animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
1173     }
1174 
1175     if (!animData.isEmpty() && (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::ColorWheel)) {
1176         mlt_prop.set("key", animData.toUtf8().constData());
1177         // This is a fake query to force the animation to be parsed
1178         (void)mlt_prop.anim_get_double("key", 0, out);
1179         return QVariant(mlt_prop.anim_get_double("key", pos.frames(pCore->getCurrentFps())));
1180     }
1181     if (!animData.isEmpty() && m_paramType == ParamType::AnimatedRect) {
1182         mlt_prop.set("key", animData.toUtf8().constData());
1183         // This is a fake query to force the animation to be parsed
1184         (void)mlt_prop.anim_get_double("key", 0, out);
1185         mlt_rect rect = mlt_prop.anim_get_rect("key", pos.frames(pCore->getCurrentFps()));
1186         QString res = QStringLiteral("%1 %2 %3 %4").arg(int(rect.x)).arg(int(rect.y)).arg(int(rect.w)).arg(int(rect.h));
1187         if (useOpacity) {
1188             res.append(QStringLiteral(" %1").arg(QString::number(rect.o, 'f')));
1189         }
1190         return QVariant(res);
1191     }
1192     if (!animData.isEmpty() && m_paramType == ParamType::Color) {
1193         mlt_prop.set("key", animData.toUtf8().constData());
1194         // This is a fake query to force the animation to be parsed
1195         (void)mlt_prop.anim_get_double("key", 0, out);
1196         mlt_color mltColor = mlt_prop.anim_get_color("key", pos.frames(pCore->getCurrentFps()));
1197         QColor color(mltColor.r, mltColor.g, mltColor.b, mltColor.a);
1198         return QVariant(QColorUtils::colorToString(color, true));
1199     }
1200     if (m_paramType == ParamType::Roto_spline) {
1201         // interpolate
1202         auto next = m_keyframeList.upper_bound(pos);
1203         if (next == m_keyframeList.cbegin()) {
1204             return (m_keyframeList.cbegin())->second.second;
1205         } else if (next == m_keyframeList.cend()) {
1206             auto it = m_keyframeList.cend();
1207             --it;
1208             return it->second.second;
1209         }
1210         auto prev = next;
1211         --prev;
1212 
1213         QSize frame = pCore->getCurrentFrameSize();
1214         QList<BPoint> p1 = RotoHelper::getPoints(prev->second.second, frame);
1215         QList<BPoint> p2 = RotoHelper::getPoints(next->second.second, frame);
1216         // relPos should be in [0,1]:
1217         // - equal to 0 on prev keyframe
1218         // - equal to 1 on next keyframe
1219         qreal relPos = 0;
1220         if (next->first != prev->first) {
1221             relPos = (pos.frames(pCore->getCurrentFps()) - prev->first.frames(pCore->getCurrentFps())) /
1222                      qreal(((next->first - prev->first).frames(pCore->getCurrentFps())));
1223         }
1224         int count = qMin(p1.count(), p2.count());
1225         QList<QVariant> vlist;
1226         for (int i = 0; i < count; ++i) {
1227             BPoint bp;
1228             QList<QVariant> pl;
1229             for (int j = 0; j < 3; ++j) {
1230                 if (p1.at(i)[j] != p2.at(i)[j]) {
1231                     bp[j] = QLineF(p1.at(i)[j], p2.at(i)[j]).pointAt(relPos);
1232                 } else {
1233                     bp[j] = p1.at(i)[j];
1234                 }
1235                 pl << QVariant(QList<QVariant>() << QVariant(bp[j].x() / frame.width()) << QVariant(bp[j].y() / frame.height()));
1236             }
1237             vlist << QVariant(pl);
1238         }
1239         return vlist;
1240     }
1241     return QVariant();
1242 }
1243 
1244 void KeyframeModel::sendModification()
1245 {
1246     if (auto ptr = m_model.lock()) {
1247         Q_ASSERT(m_index.isValid());
1248         QString name = ptr->data(m_index, AssetParameterModel::NameRole).toString();
1249         if (AssetParameterModel::isAnimated(m_paramType)) {
1250             m_lastData = getAnimProperty();
1251             ptr->setParameter(name, m_lastData, false, m_index);
1252         } else {
1253             Q_ASSERT(false); // Not implemented, TODO
1254         }
1255     }
1256 }
1257 
1258 QString KeyframeModel::realValue(double normalizedValue) const
1259 {
1260     double value = getNormalizedValue(normalizedValue).toDouble();
1261     if (auto ptr = m_model.lock()) {
1262         int decimals = ptr->data(m_index, AssetParameterModel::DecimalsRole).toInt();
1263         value *= ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
1264         QString result;
1265         if (decimals == 0) {
1266             if (m_paramType == ParamType::AnimatedRect) {
1267                 value = qRound(value * 100.);
1268             }
1269             // Fix rounding erros in double > int conversion
1270             if (value > 0.) {
1271                 value += 0.001;
1272             } else {
1273                 value -= 0.001;
1274             }
1275             result = QString::number(int(value));
1276         } else {
1277             result = QString::number(value, 'f', decimals);
1278         }
1279         result.append(ptr->data(m_index, AssetParameterModel::SuffixRole).toString());
1280         return result;
1281     }
1282     return QString::number(value);
1283 }
1284 
1285 void KeyframeModel::refresh(int in, int out)
1286 {
1287     Q_ASSERT(m_index.isValid());
1288     QString animData;
1289     if (auto ptr = m_model.lock()) {
1290         animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
1291     } else {
1292         qDebug() << "WARNING : unable to access keyframe's model";
1293         return;
1294     }
1295     if (animData == m_lastData) {
1296         // nothing to do
1297         // qDebug() << "// DATA WAS ALREADY PARSED, ABORTING REFRESH\n";
1298         return;
1299     }
1300     if (m_paramType == ParamType::Roto_spline) {
1301         parseRotoProperty(animData);
1302     } else if (AssetParameterModel::isAnimated(m_paramType)) {
1303         parseAnimProperty(animData, in, out);
1304     } else {
1305         // first, try to convert to double
1306         bool ok = false;
1307         double value = animData.toDouble(&ok);
1308         if (ok) {
1309             Fun undo = []() { return true; };
1310             Fun redo = []() { return true; };
1311             addKeyframe(GenTime(), KeyframeType::Linear, QVariant(value), false, undo, redo);
1312         } else {
1313             Q_ASSERT(false); // Not implemented, TODO
1314         }
1315     }
1316     m_lastData = animData;
1317 }
1318 
1319 void KeyframeModel::reset()
1320 {
1321     Q_ASSERT(m_index.isValid());
1322     QString animData;
1323     if (auto ptr = m_model.lock()) {
1324         animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
1325     } else {
1326         qDebug() << "WARNING : unable to access keyframe's model";
1327         return;
1328     }
1329     if (animData == m_lastData) {
1330         // nothing to do
1331         qDebug() << "// DATA WAS ALREADY PARSED, ABORTING\n_________________";
1332         return;
1333     }
1334     if (m_paramType == ParamType::Roto_spline) {
1335         // TODO: resetRotoProperty(animData);
1336     } else if (AssetParameterModel::isAnimated(m_paramType)) {
1337         qDebug() << "parsing keyframe" << animData;
1338         resetAnimProperty(animData);
1339     } else {
1340         // first, try to convert to double
1341         bool ok = false;
1342         double value = animData.toDouble(&ok);
1343         if (ok) {
1344             Fun undo = []() { return true; };
1345             Fun redo = []() { return true; };
1346             addKeyframe(GenTime(), KeyframeType::Linear, QVariant(value), false, undo, redo);
1347             PUSH_UNDO(undo, redo, i18n("Reset effect"));
1348             qDebug() << "KEYFRAME ADDED" << value;
1349         } else {
1350             Q_ASSERT(false); // Not implemented, TODO
1351         }
1352     }
1353     m_lastData = animData;
1354 }
1355 
1356 QList<QPoint> KeyframeModel::getRanges(const QString &animData, const std::shared_ptr<AssetParameterModel> &model)
1357 {
1358     Mlt::Properties mlt_prop;
1359     model->passProperties(mlt_prop);
1360     mlt_prop.set("key", animData.toUtf8().constData());
1361     // This is a fake query to force the animation to be parsed
1362     (void)mlt_prop.anim_get_int("key", 0, 0);
1363 
1364     Mlt::Animation anim = mlt_prop.get_animation("key");
1365     int frame;
1366     mlt_keyframe_type type;
1367     anim.key_get(0, frame, type);
1368     mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
1369     QPoint pX(int(rect.x), int(rect.x));
1370     QPoint pY(int(rect.y), int(rect.y));
1371     QPoint pW(int(rect.w), int(rect.w));
1372     QPoint pH(int(rect.h), int(rect.h));
1373     QPoint pO(int(rect.o), int(rect.o));
1374     for (int i = 1; i < anim.key_count(); ++i) {
1375         anim.key_get(i, frame, type);
1376         if (!animData.contains(QLatin1Char('='))) {
1377             // TODO: use a default user defined type
1378             type = mlt_keyframe_linear;
1379         }
1380         rect = mlt_prop.anim_get_rect("key", frame);
1381         pX.setX(qMin(int(rect.x), pX.x()));
1382         pX.setY(qMax(int(rect.x), pX.y()));
1383         pY.setX(qMin(int(rect.y), pY.x()));
1384         pY.setY(qMax(int(rect.y), pY.y()));
1385         pW.setX(qMin(int(rect.w), pW.x()));
1386         pW.setY(qMax(int(rect.w), pW.y()));
1387         pH.setX(qMin(int(rect.h), pH.x()));
1388         pH.setY(qMax(int(rect.h), pH.y()));
1389         pO.setX(qMin(int(rect.o), pO.x()));
1390         pO.setY(qMax(int(rect.o), pO.y()));
1391         // value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(locale.toString(rect.o)));
1392     }
1393     QList<QPoint> result{pX, pY, pW, pH, pO};
1394     return result;
1395 }
1396 
1397 std::shared_ptr<Mlt::Properties> KeyframeModel::getAnimation(std::shared_ptr<AssetParameterModel> model, const QString &animData, int duration)
1398 {
1399     std::shared_ptr<Mlt::Properties> mlt_prop(new Mlt::Properties());
1400     model->passProperties(*mlt_prop.get());
1401     mlt_prop->set("key", animData.toUtf8().constData());
1402     // This is a fake query to force the animation to be parsed
1403     (void)mlt_prop->anim_get_rect("key", 0, duration);
1404     return mlt_prop;
1405 }
1406 
1407 const QString KeyframeModel::getAnimationStringWithOffset(std::shared_ptr<AssetParameterModel> model, const QString &animData, int offset, int duration,
1408                                                           ParamType paramType, bool useOpacity)
1409 {
1410     Mlt::Properties mlt_prop;
1411     model->passProperties(mlt_prop);
1412     mlt_prop.set("key", animData.toUtf8().constData());
1413     // This is a fake query to force the animation to be parsed
1414     (void)mlt_prop.anim_get_rect("key", 0);
1415     Mlt::Animation anim = mlt_prop.get_animation("key");
1416     if (offset > 0) {
1417         for (int i = anim.key_count() - 1; i >= 0; --i) {
1418             int pos = anim.key_get_frame(i) + offset;
1419             anim.key_set_frame(i, pos);
1420         }
1421     } else if (offset < 0) {
1422         for (int i = 0; i < anim.key_count(); ++i) {
1423             int pos = anim.key_get_frame(i) + offset;
1424             if (pos >= 0) {
1425                 anim.key_set_frame(i, pos);
1426             }
1427         }
1428     }
1429     // If last key is beyond duration, add new keyframe at end
1430     int lastPos = anim.key_get_frame(anim.key_count() - 1);
1431     if (lastPos > duration) {
1432         QVariant value;
1433         switch (paramType) {
1434         case ParamType::AnimatedRect: {
1435             mlt_rect rect = mlt_prop.anim_get_rect("key", duration);
1436             QString res = QStringLiteral("%1 %2 %3 %4").arg(int(rect.x)).arg(int(rect.y)).arg(int(rect.w)).arg(int(rect.h));
1437             if (useOpacity) {
1438                 res.append(QStringLiteral(" %1").arg(QString::number(rect.o, 'f')));
1439             }
1440             value = QVariant(res);
1441             break;
1442         }
1443         case ParamType::Color: {
1444             mlt_color mltColor = mlt_prop.anim_get_color("key", duration);
1445             QColor color(mltColor.r, mltColor.g, mltColor.b, mltColor.a);
1446             value = QVariant(QColorUtils::colorToString(color, true));
1447             break;
1448         }
1449         default:
1450             value = QVariant(mlt_prop.anim_get_double("key", duration));
1451             break;
1452         }
1453         mlt_prop.anim_set("key", value.toString().toUtf8().constData(), duration);
1454         // Ensure the added keyframe uses the same type as last one
1455         mlt_keyframe_type lastType = anim.key_get_type(anim.key_count() - 1);
1456         for (int i = 0; i < anim.key_count(); i++) {
1457             if (anim.key_get_frame(i) == duration) {
1458                 anim.key_set_type(i, lastType);
1459                 break;
1460             }
1461         }
1462     }
1463     return qstrdup(anim.serialize_cut(0, duration));
1464 }
1465 
1466 QList<GenTime> KeyframeModel::getKeyframePos() const
1467 {
1468     QList<GenTime> all_pos;
1469     for (const auto &m : m_keyframeList) {
1470         all_pos.push_back(m.first);
1471     }
1472     return all_pos;
1473 }
1474 
1475 bool KeyframeModel::removeNextKeyframes(GenTime pos, Fun &undo, Fun &redo)
1476 {
1477     QWriteLocker locker(&m_lock);
1478     std::vector<GenTime> all_pos;
1479     Fun local_undo = []() { return true; };
1480     Fun local_redo = []() { return true; };
1481     for (const auto &m : m_keyframeList) {
1482         if (m.first >= pos && m.first != m_keyframeList.begin()->first) {
1483             all_pos.push_back(m.first);
1484         }
1485     }
1486     std::sort(all_pos.begin(), all_pos.end());
1487     int kfrCount = int(m_keyframeList.size());
1488     // Remove deleted keyframes from selection
1489     if (auto ptr = m_model.lock()) {
1490         QVector<int> selection;
1491         for (auto &ix : ptr->m_selectedKeyframes) {
1492             if (ix < kfrCount) {
1493                 selection << ix;
1494             }
1495         }
1496         ptr->m_selectedKeyframes = selection;
1497     }
1498     // we trigger only one global remove/insertrow event
1499     int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(all_pos.front())));
1500     Fun update_redo_start = [this, row, kfrCount]() {
1501         beginRemoveRows(QModelIndex(), row, kfrCount - 1);
1502         return true;
1503     };
1504     Fun update_redo_end = [this]() {
1505         endRemoveRows();
1506         return true;
1507     };
1508     Fun update_undo_start = [this, row, kfrCount]() {
1509         beginInsertRows(QModelIndex(), row, kfrCount - 1);
1510         return true;
1511     };
1512     Fun update_undo_end = [this]() {
1513         endInsertRows();
1514         return true;
1515     };
1516     PUSH_LAMBDA(update_redo_start, local_redo);
1517     PUSH_LAMBDA(update_undo_start, local_undo);
1518     update_redo_start();
1519     for (const auto &p : all_pos) {
1520         bool res = removeKeyframe(p, local_undo, local_redo, false);
1521         if (!res) {
1522             bool undone = local_undo();
1523             Q_ASSERT(undone);
1524             return false;
1525         }
1526     }
1527     update_redo_end();
1528     PUSH_LAMBDA(update_redo_end, local_redo);
1529     PUSH_LAMBDA(update_undo_end, local_undo);
1530     UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
1531     return true;
1532 }
1533 
1534 void KeyframeModel::setSelectedKeyframe(int ix, bool add)
1535 {
1536     QVector<int> previous;
1537     if (auto ptr = m_model.lock()) {
1538         if (add) {
1539             if (ptr->m_selectedKeyframes.contains(ix)) {
1540                 // remove from selection
1541                 ptr->m_selectedKeyframes.removeAll(ix);
1542             } else {
1543                 ptr->m_selectedKeyframes << ix;
1544             }
1545         } else {
1546             previous = ptr->m_selectedKeyframes;
1547             ptr->m_selectedKeyframes = {ix};
1548         }
1549     }
1550     if (!add) {
1551         for (auto &ix2 : previous) {
1552             if (ix2 > -1) {
1553                 Q_EMIT requestModelUpdate(index(ix2), index(ix2), {SelectedRole});
1554             }
1555         }
1556     }
1557     if (ix > -1) {
1558         Q_EMIT requestModelUpdate(index(ix), index(ix), {SelectedRole});
1559     }
1560 }
1561 
1562 void KeyframeModel::setSelectedKeyframes(QVector<int> selection)
1563 {
1564     QVector<int> previous;
1565     selection.removeAll(-1);
1566     std::sort(selection.begin(), selection.end());
1567     if (auto ptr = m_model.lock()) {
1568         previous = ptr->m_selectedKeyframes;
1569         ptr->m_selectedKeyframes = selection;
1570     }
1571     if (!selection.isEmpty()) {
1572         Q_EMIT requestModelUpdate(index(selection.first()), index(selection.last()), {SelectedRole});
1573     }
1574     for (auto &ix : previous) {
1575         if (ix > -1 && !selection.contains(ix)) {
1576             Q_EMIT requestModelUpdate(index(ix), index(ix), {SelectedRole});
1577         }
1578     }
1579 }
1580 
1581 int KeyframeModel::activeKeyframe() const
1582 {
1583     if (auto ptr = m_model.lock()) {
1584         return ptr->m_activeKeyframe;
1585     }
1586     return -1;
1587 }
1588 
1589 void KeyframeModel::setActiveKeyframe(int ix)
1590 {
1591     int oldActive = -1;
1592     if (auto ptr = m_model.lock()) {
1593         oldActive = ptr->m_activeKeyframe;
1594         if (oldActive == ix) {
1595             // Keyframe already active
1596             return;
1597         }
1598         ptr->m_activeKeyframe = ix;
1599     }
1600     Q_EMIT requestModelUpdate(index(ix), index(ix), {ActiveRole});
1601     if (oldActive > -1) {
1602         Q_EMIT requestModelUpdate(index(oldActive), index(oldActive), {ActiveRole});
1603     }
1604 }
1605 
1606 int KeyframeModel::getIndexForPos(const GenTime pos) const
1607 {
1608     if (m_keyframeList.count(pos) == 0) {
1609         return -1;
1610     }
1611     return static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
1612 }