File indexing completed on 2024-12-08 04:25:57

0001 /*
0002     SPDX-FileCopyrightText: 2017 Nicolas Carion
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "assetparametermodel.hpp"
0007 #include "assets/keyframes/model/keyframemodellist.hpp"
0008 #include "core.h"
0009 #include "effects/effectsrepository.hpp"
0010 #include "kdenlivesettings.h"
0011 #include "klocalizedstring.h"
0012 #include "profiles/profilemodel.hpp"
0013 #include <QDebug>
0014 #include <QDir>
0015 #include <QDirIterator>
0016 #include <QJsonArray>
0017 #include <QJsonObject>
0018 #include <QRegularExpression>
0019 #include <QString>
0020 #define DEBUG_LOCALE false
0021 
0022 AssetParameterModel::AssetParameterModel(std::unique_ptr<Mlt::Properties> asset, const QDomElement &assetXml, const QString &assetId, ObjectId ownerId,
0023                                          const QString &originalDecimalPoint, QObject *parent)
0024     : QAbstractListModel(parent)
0025     , monitorId(ownerId.type == KdenliveObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor)
0026     , m_assetId(assetId)
0027     , m_ownerId(ownerId)
0028     , m_active(false)
0029     , m_asset(std::move(asset))
0030     , m_keyframes(nullptr)
0031     , m_activeKeyframe(-1)
0032     , m_filterProgress(0)
0033 {
0034     Q_ASSERT(m_asset->is_valid());
0035     QDomNodeList parameterNodes = assetXml.elementsByTagName(QStringLiteral("parameter"));
0036     m_hideKeyframesByDefault = assetXml.hasAttribute(QStringLiteral("hideKeyframes"));
0037     m_requiresInOut = assetXml.hasAttribute(QStringLiteral("requires_in_out"));
0038     m_isAudio = assetXml.attribute(QStringLiteral("type")) == QLatin1String("audio");
0039 
0040     bool needsLocaleConversion = false;
0041     QString separator;
0042     QString oldSeparator;
0043     // Check locale, default effects xml has no LC_NUMERIC defined and always uses the C locale
0044     if (assetXml.hasAttribute(QStringLiteral("LC_NUMERIC"))) {
0045         QLocale effectLocale = QLocale(assetXml.attribute(QStringLiteral("LC_NUMERIC"))); // Check if effect has a special locale → probably OK
0046         if (QLocale::c().decimalPoint() != effectLocale.decimalPoint()) {
0047             needsLocaleConversion = true;
0048 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0049             separator = QString(QLocale::c().decimalPoint());
0050             oldSeparator = QString(effectLocale.decimalPoint());
0051 #else
0052             separator = QLocale::c().decimalPoint();
0053             oldSeparator = effectLocale.decimalPoint();
0054 #endif
0055         }
0056     }
0057 
0058 #if false
0059     // Debut test  stuff. Warning, assets can also come from TransitionsRepository depending on owner type
0060     if (EffectsRepository::get()->exists(assetId)) {
0061         qDebug() << "Asset " << assetId << " found in the repository. Description: " << EffectsRepository::get()->getDescription(assetId);
0062         QString str;
0063         QTextStream stream(&str);
0064         EffectsRepository::get()->getXml(assetId).save(stream, 4);
0065         qDebug() << "Asset XML: " << str;
0066     } else {
0067         qDebug() << "Asset not found in repo: " << assetId;
0068     }
0069 #endif
0070 
0071     qDebug() << "XML parsing of " << assetId << ". found" << parameterNodes.count() << "parameters";
0072 
0073     if (DEBUG_LOCALE) {
0074         QString str;
0075         QTextStream stream(&str);
0076         assetXml.save(stream, 1);
0077         qDebug() << "XML to parse: " << str;
0078     }
0079     bool fixDecimalPoint = !originalDecimalPoint.isEmpty();
0080     if (fixDecimalPoint) {
0081         qDebug() << "Original decimal point was different:" << originalDecimalPoint << "Values will be converted if required.";
0082     }
0083     for (int i = 0; i < parameterNodes.count(); ++i) {
0084         QDomElement currentParameter = parameterNodes.item(i).toElement();
0085 
0086         // Convert parameters if we need to
0087         // Note: This is not directly related to the originalDecimalPoint parameter.
0088         // Is it still required? Does it work correctly for non-number values (e.g. lists which contain commas)?
0089         if (needsLocaleConversion) {
0090             QDomNamedNodeMap attrs = currentParameter.attributes();
0091             for (int k = 0; k < attrs.count(); ++k) {
0092                 QString nodeName = attrs.item(k).nodeName();
0093                 if (nodeName != QLatin1String("type") && nodeName != QLatin1String("name")) {
0094                     QString val = attrs.item(k).nodeValue();
0095                     if (val.contains(oldSeparator)) {
0096                         QString newVal = val.replace(oldSeparator, separator);
0097                         attrs.item(k).setNodeValue(newVal);
0098                     }
0099                 }
0100             }
0101         }
0102         // Parse the basic attributes of the parameter
0103         QString name = currentParameter.attribute(QStringLiteral("name"));
0104         QString type = currentParameter.attribute(QStringLiteral("type"));
0105         QString value = currentParameter.attribute(QStringLiteral("value"));
0106         ParamRow currentRow;
0107         currentRow.type = paramTypeFromStr(type);
0108         currentRow.xml = currentParameter;
0109         if (value.isEmpty()) {
0110             QVariant defaultValue = parseAttribute(m_ownerId, QStringLiteral("default"), currentParameter);
0111             value = defaultValue.toString();
0112             qDebug() << "QLocale: Default value is" << defaultValue << "parsed:" << value;
0113         }
0114         bool isFixed = (type == QLatin1String("fixed"));
0115         if (isFixed) {
0116             m_fixedParams[name] = value;
0117         } else if (currentRow.type == ParamType::Position) {
0118             int val = value.toInt();
0119             if (val < 0) {
0120                 int in = pCore->getItemIn(m_ownerId);
0121                 int out = in + pCore->getItemDuration(m_ownerId) - 1;
0122                 val += out;
0123                 value = QString::number(val);
0124             }
0125         } else if (isAnimated(currentRow.type) && currentRow.type != ParamType::Roto_spline) {
0126             // Roto_spline keyframes are stored as JSON so do not apply this to roto
0127             if (!value.contains(QLatin1Char('='))) {
0128                 value.prepend(QStringLiteral("%1=").arg(pCore->getItemIn(m_ownerId)));
0129             }
0130         }
0131 
0132         if (fixDecimalPoint) {
0133             bool converted = true;
0134             QString originalValue(value);
0135             switch (currentRow.type) {
0136             case ParamType::KeyframeParam:
0137             case ParamType::Position:
0138                 // Fix values like <position>=1,5
0139                 value.replace(QRegularExpression(R"((=\d+),(\d+))"), "\\1.\\2");
0140                 break;
0141             case ParamType::AnimatedRect:
0142                 // Fix values like <position>=50 20 1920 1080 0,75
0143                 value.replace(QRegularExpression(R"((=\d+ \d+ \d+ \d+ \d+),(\d+))"), "\\1.\\2");
0144                 break;
0145             case ParamType::ColorWheel:
0146                 // Colour wheel has 3 separate properties: prop_r, prop_g and prop_b, always numbers
0147             case ParamType::Double:
0148             case ParamType::Hidden:
0149             case ParamType::List:
0150             case ParamType::ListWithDependency:
0151                 // Despite its name, a list type parameter is a single value *chosen from* a list.
0152                 // If it contains a non-“.” decimal separator, it is very likely wrong.
0153                 // Fall-through, treat like Double
0154             case ParamType::Bezier_spline:
0155                 value.replace(originalDecimalPoint, ".");
0156                 break;
0157             case ParamType::Bool:
0158             case ParamType::FixedColor:
0159             case ParamType::Color:
0160             case ParamType::Fontfamily:
0161             case ParamType::Keywords:
0162             case ParamType::Readonly:
0163             case ParamType::Url:
0164             case ParamType::UrlList:
0165                 // All fine
0166                 converted = false;
0167                 break;
0168             case ParamType::Curve:
0169             case ParamType::Geometry:
0170             case ParamType::Switch:
0171             case ParamType::MultiSwitch:
0172             case ParamType::Wipe:
0173                 // Pretty sure that those are fine
0174                 converted = false;
0175                 break;
0176             case ParamType::Roto_spline: // Not sure because cannot test
0177             case ParamType::Filterjob:
0178                 // Not sure if fine
0179                 converted = false;
0180                 break;
0181             }
0182             if (converted) {
0183                 if (value != originalValue) {
0184                     qDebug() << "Decimal point conversion: " << name << "converted from" << originalValue << "to" << value;
0185                 } else {
0186                     qDebug() << "Decimal point conversion: " << name << " is already ok: " << value;
0187                 }
0188             } else {
0189                 qDebug() << "No fixing needed for" << name << "=" << value;
0190             }
0191         }
0192 
0193         if (!isFixed) {
0194             currentRow.value = value;
0195             QString title = i18n(currentParameter.firstChildElement(QStringLiteral("name")).text().toUtf8().data());
0196             if (title.isEmpty() || title == QStringLiteral("(I18N_EMPTY_MESSAGE)")) {
0197                 title = name;
0198             }
0199             currentRow.name = title;
0200             m_params[name] = currentRow;
0201         }
0202         if (!name.isEmpty()) {
0203             internalSetParameter(name, value);
0204             // Keep track of param order
0205             m_paramOrder.push_back(name);
0206         }
0207 
0208         if (isFixed) {
0209             // fixed parameters are not displayed so we don't store them.
0210             continue;
0211         }
0212         m_rows.push_back(name);
0213     }
0214     if (m_assetId.startsWith(QStringLiteral("sox_"))) {
0215         // Sox effects need to have a special "Effect" value set
0216         QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
0217         for (const QString &pName : m_paramOrder) {
0218             effectParam << m_asset->get(pName.toUtf8().constData());
0219         }
0220         m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
0221     }
0222 
0223     qDebug() << "END parsing of " << assetId << ". Number of found parameters" << m_rows.size();
0224     Q_EMIT modelChanged();
0225 }
0226 
0227 void AssetParameterModel::prepareKeyframes(int in, int out)
0228 {
0229     if (m_keyframes) return;
0230     int ix = 0;
0231     for (const auto &name : qAsConst(m_rows)) {
0232         if (isAnimated(m_params.at(name).type)) {
0233             addKeyframeParam(index(ix, 0), in, out);
0234         }
0235         ix++;
0236     }
0237     if (m_keyframes) {
0238         // Make sure we have keyframes at same position for all parameters
0239         m_keyframes->checkConsistency();
0240     }
0241 }
0242 
0243 QMap<QString, std::pair<ParamType, bool>> AssetParameterModel::getKeyframableParameters() const
0244 {
0245     // QMap<QString, std::pair<ParamType, bool>> paramNames;
0246     QMap<QString, std::pair<ParamType, bool>> paramNames;
0247     for (const auto &name : m_rows) {
0248         ParamType type = m_params.at(name).type;
0249         if (isAnimated(type) && type != ParamType::Roto_spline) {
0250             // addKeyframeParam(index(ix, 0));
0251             bool useOpacity = m_params.at(name).xml.attribute(QStringLiteral("opacity")) != QLatin1String("false");
0252             paramNames.insert(name, {type, useOpacity});
0253             // paramNames << name;
0254         }
0255     }
0256     return paramNames;
0257 }
0258 
0259 const QString AssetParameterModel::getParam(const QString &paramName)
0260 {
0261     Q_ASSERT(m_asset->is_valid());
0262     return m_asset->get(paramName.toUtf8().constData());
0263 }
0264 
0265 void AssetParameterModel::setParameter(const QString &name, int value, bool update)
0266 {
0267     Q_ASSERT(m_asset->is_valid());
0268     m_asset->set(name.toLatin1().constData(), value);
0269     if (m_fixedParams.count(name) == 0) {
0270         m_params[name].value = value;
0271     } else {
0272         m_fixedParams[name] = value;
0273     }
0274     if (m_assetId.startsWith(QStringLiteral("sox_"))) {
0275         // Warning, SOX effect, need unplug/replug
0276         qDebug() << "// Warning, SOX effect, need unplug/replug";
0277         QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
0278         for (const QString &pName : m_paramOrder) {
0279             effectParam << m_asset->get(pName.toUtf8().constData());
0280         }
0281         m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
0282         Q_EMIT replugEffect(shared_from_this());
0283     } else if (m_assetId.startsWith(QStringLiteral("ladspa"))) {
0284         // these effects don't understand param change and need to be rebuild
0285         Q_EMIT replugEffect(shared_from_this());
0286     }
0287     if (update) {
0288         Q_EMIT modelChanged();
0289         Q_EMIT dataChanged(index(0, 0), index(m_rows.count() - 1, 0), {});
0290         // Update fades in timeline
0291         pCore->updateItemModel(m_ownerId, m_assetId);
0292         if (!m_isAudio) {
0293             // Trigger monitor refresh
0294             pCore->refreshProjectItem(m_ownerId);
0295             // Invalidate timeline preview
0296             pCore->invalidateItem(m_ownerId);
0297         }
0298     }
0299 }
0300 
0301 void AssetParameterModel::internalSetParameter(const QString name, const QString paramValue, const QModelIndex &paramIndex)
0302 {
0303     Q_ASSERT(m_asset->is_valid());
0304     // TODO: this does not really belong here, but I don't see another way to do it so that undo works
0305     if (m_params.count(name) > 0) {
0306         ParamType type = m_params.at(name).type;
0307         if (type == ParamType::Curve) {
0308             QStringList vals = paramValue.split(QLatin1Char(';'), Qt::SkipEmptyParts);
0309             int points = vals.size();
0310             m_asset->set("3", points / 10.);
0311             m_params[QStringLiteral("3")].value = points / 10.;
0312             // for the curve, inpoints are numbered: 6, 8, 10, 12, 14
0313             // outpoints, 7, 9, 11, 13,15 so we need to deduce these enums
0314             for (int i = 0; i < points; i++) {
0315                 const QString &pointVal = vals.at(i);
0316                 int idx = 2 * i + 6;
0317                 QString pName = QString::number(idx);
0318                 double val = pointVal.section(QLatin1Char('/'), 0, 0).toDouble();
0319                 m_asset->set(pName.toLatin1().constData(), val);
0320                 m_params[pName].value = val;
0321                 idx++;
0322                 pName = QString::number(idx);
0323                 val = pointVal.section(QLatin1Char('/'), 1, 1).toDouble();
0324                 m_asset->set(pName.toLatin1().constData(), val);
0325                 m_params[pName].value = val;
0326             }
0327         } else if (type == ParamType::MultiSwitch) {
0328             QStringList names = name.split(QLatin1Char('\n'));
0329             QStringList values = paramValue.split(QLatin1Char('\n'));
0330             if (names.count() == values.count()) {
0331                 for (int i = 0; i < names.count(); i++) {
0332                     m_asset->set(names.at(i).toLatin1().constData(), values.at(i).toLatin1().constData());
0333                 }
0334                 m_params[name].value = paramValue;
0335             }
0336             return;
0337         }
0338     }
0339     bool conversionSuccess = true;
0340     double doubleValue = paramValue.toDouble(&conversionSuccess);
0341     if (conversionSuccess) {
0342         m_asset->set(name.toLatin1().constData(), doubleValue);
0343         if (m_fixedParams.count(name) == 0) {
0344             m_params[name].value = doubleValue;
0345         } else {
0346             m_fixedParams[name] = doubleValue;
0347         }
0348     } else {
0349         m_asset->set(name.toLatin1().constData(), paramValue.toUtf8().constData());
0350         if (m_fixedParams.count(name) == 0) {
0351             m_params[name].value = paramValue;
0352             if (m_keyframes) {
0353                 // This is a fake query to force the animation to be parsed
0354                 (void)m_asset->anim_get_int(name.toLatin1().constData(), 0, -1);
0355                 KeyframeModel *km = m_keyframes->getKeyModel(paramIndex);
0356                 if (km) {
0357                     km->refresh();
0358                 } else {
0359                     qDebug() << "====ERROR KFMODEL NOT FOUND FOR: " << name << ", " << paramIndex;
0360                 }
0361                 // m_keyframes->refresh();
0362             }
0363         } else {
0364             m_fixedParams[name] = paramValue;
0365         }
0366     }
0367     // Fades need to have their alpha or level param synced to in/out
0368     if (m_assetId.startsWith(QLatin1String("fade_")) && (name == QLatin1String("in") || name == QLatin1String("out"))) {
0369         if (m_assetId.startsWith(QLatin1String("fade_from"))) {
0370             if (getAsset()->get("alpha") == QLatin1String("1")) {
0371                 // Adjust level value to match filter end
0372                 getAsset()->set("level", "0=0;-1=1");
0373             } else if (getAsset()->get("level") == QLatin1String("1")) {
0374                 getAsset()->set("alpha", "0=0;-1=1");
0375             }
0376         } else {
0377             if (getAsset()->get("alpha") == QLatin1String("1")) {
0378                 // Adjust level value to match filter end
0379                 getAsset()->set("level", "0=1;-1=0");
0380             } else if (getAsset()->get("level") == QLatin1String("1")) {
0381                 getAsset()->set("alpha", "0=1;-1=0");
0382             }
0383         }
0384     }
0385     // qDebug() << " = = SET EFFECT PARAM: " << name << " = " << m_asset->get(name.toLatin1().constData());
0386 }
0387 
0388 void AssetParameterModel::setParameter(const QString &name, const QString &paramValue, bool update, const QModelIndex &paramIndex)
0389 {
0390     // qDebug() << "// PROCESSING PARAM CHANGE: " << name << ", UPDATE: " << update << ", VAL: " << paramValue;
0391     internalSetParameter(name, paramValue, paramIndex);
0392     bool updateChildRequired = true;
0393     if (m_assetId.startsWith(QStringLiteral("sox_"))) {
0394         // Warning, SOX effect, need unplug/replug
0395         QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
0396         for (const QString &pName : m_paramOrder) {
0397             effectParam << m_asset->get(pName.toUtf8().constData());
0398         }
0399         m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
0400         Q_EMIT replugEffect(shared_from_this());
0401         updateChildRequired = false;
0402     } else if (m_assetId.startsWith(QStringLiteral("ladspa"))) {
0403         // these effects don't understand param change and need to be rebuild
0404         Q_EMIT replugEffect(shared_from_this());
0405         updateChildRequired = false;
0406     } else if (update) {
0407         qDebug() << "// SENDING DATA CHANGE....";
0408         if (paramIndex.isValid()) {
0409             Q_EMIT dataChanged(paramIndex, paramIndex);
0410         } else {
0411             QModelIndex ix = index(m_rows.indexOf(name), 0);
0412             Q_EMIT dataChanged(ix, ix);
0413         }
0414         Q_EMIT modelChanged();
0415     }
0416     if (updateChildRequired) {
0417         Q_EMIT updateChildren({name});
0418     }
0419     // Update timeline view if necessary
0420     if (m_ownerId.type == KdenliveObjectType::NoItem) {
0421         // Used for generator clips
0422         if (!update) Q_EMIT modelChanged();
0423     } else {
0424         // Update fades in timeline
0425         pCore->updateItemModel(m_ownerId, m_assetId);
0426         if (!m_isAudio) {
0427             // Trigger monitor refresh
0428             pCore->refreshProjectItem(m_ownerId);
0429             // Invalidate timeline preview
0430             pCore->invalidateItem(m_ownerId);
0431         }
0432     }
0433 }
0434 
0435 AssetParameterModel::~AssetParameterModel() = default;
0436 
0437 QVariant AssetParameterModel::data(const QModelIndex &index, int role) const
0438 {
0439     const QVector<int> bypassRoles = {AssetParameterModel::InRole,
0440                                       AssetParameterModel::OutRole,
0441                                       AssetParameterModel::ParentInRole,
0442                                       AssetParameterModel::ParentDurationRole,
0443                                       AssetParameterModel::ParentPositionRole,
0444                                       AssetParameterModel::RequiresInOut,
0445                                       AssetParameterModel::HideKeyframesFirstRole};
0446 
0447     if (bypassRoles.contains(role)) {
0448         switch (role) {
0449         case InRole:
0450             return m_asset->get_int("in");
0451         case OutRole:
0452             return m_asset->get_int("out");
0453         case ParentInRole:
0454             return pCore->getItemIn(m_ownerId);
0455         case ParentDurationRole:
0456             if (m_asset->get_int("kdenlive:force_in_out") == 1) {
0457                 // Zone effect, return effect length
0458                 return m_asset->get_int("out") - m_asset->get_int("in");
0459             }
0460             return pCore->getItemDuration(m_ownerId);
0461         case ParentPositionRole:
0462             return pCore->getItemPosition(m_ownerId);
0463         case HideKeyframesFirstRole:
0464             return m_hideKeyframesByDefault;
0465         case RequiresInOut:
0466             return m_requiresInOut;
0467         default:
0468             qDebug() << "WARNING; UNHANDLED DATA: " << role;
0469             return QVariant();
0470         }
0471     }
0472     if (index.row() < 0 || index.row() >= m_rows.size() || !index.isValid()) {
0473         return QVariant();
0474     }
0475     QString paramName = m_rows[index.row()];
0476     Q_ASSERT(m_params.count(paramName) > 0);
0477     const QDomElement &element = m_params.at(paramName).xml;
0478     switch (role) {
0479     case Qt::DisplayRole:
0480     case Qt::EditRole:
0481         return m_params.at(paramName).name;
0482     case NameRole:
0483         return paramName;
0484     case TypeRole:
0485         return QVariant::fromValue<ParamType>(m_params.at(paramName).type);
0486     case CommentRole: {
0487         QDomElement commentElem = element.firstChildElement(QStringLiteral("comment"));
0488         QString comment;
0489         if (!commentElem.isNull()) {
0490             comment = i18n(commentElem.text().toUtf8().data());
0491         }
0492         return comment;
0493     }
0494     case MinRole:
0495         return parseAttribute(m_ownerId, QStringLiteral("min"), element);
0496     case MaxRole:
0497         return parseAttribute(m_ownerId, QStringLiteral("max"), element);
0498     case FactorRole:
0499         return parseAttribute(m_ownerId, QStringLiteral("factor"), element, 1);
0500     case ScaleRole:
0501         return parseAttribute(m_ownerId, QStringLiteral("scale"), element, 0);
0502     case DecimalsRole:
0503         return parseAttribute(m_ownerId, QStringLiteral("decimals"), element);
0504     case OddRole:
0505         return element.attribute(QStringLiteral("odd")) == QLatin1String("1");
0506     case VisualMinRole:
0507         return parseAttribute(m_ownerId, QStringLiteral("visualmin"), element);
0508     case VisualMaxRole:
0509         return parseAttribute(m_ownerId, QStringLiteral("visualmax"), element);
0510     case DefaultRole:
0511         return parseAttribute(m_ownerId, QStringLiteral("default"), element);
0512     case FilterRole:
0513         return parseAttribute(m_ownerId, QStringLiteral("filter"), element);
0514     case FilterParamsRole:
0515         return parseAttribute(m_ownerId, QStringLiteral("filterparams"), element);
0516     case FilterConsumerParamsRole:
0517         return parseAttribute(m_ownerId, QStringLiteral("consumerparams"), element);
0518     case FilterJobParamsRole:
0519         return parseSubAttributes(QStringLiteral("jobparam"), element);
0520     case FilterProgressRole:
0521         return m_filterProgress;
0522     case AlternateNameRole: {
0523         QDomNode child = element.firstChildElement(QStringLiteral("name"));
0524         if (child.toElement().hasAttribute(QStringLiteral("conditional"))) {
0525             return child.toElement().attribute(QStringLiteral("conditional"));
0526         }
0527         return m_params.at(paramName).name;
0528     }
0529     case SuffixRole:
0530         return element.attribute(QStringLiteral("suffix"));
0531     case OpacityRole:
0532         return element.attribute(QStringLiteral("opacity")) != QLatin1String("false");
0533     case RelativePosRole:
0534         return element.attribute(QStringLiteral("relative")) == QLatin1String("true");
0535     case ToTimePosRole:
0536         return element.attribute(QStringLiteral("totime")) == QLatin1String("true");
0537     case ShowInTimelineRole:
0538         return !element.hasAttribute(QStringLiteral("notintimeline"));
0539     case AlphaRole:
0540         return element.attribute(QStringLiteral("alpha")) == QLatin1String("1");
0541     case ValueRole: {
0542         if (m_params.at(paramName).type == ParamType::MultiSwitch) {
0543             // Multi params concatenate param names with a '\n' and param values with a space
0544             QStringList paramNames = paramName.split(QLatin1Char('\n'));
0545             QStringList values;
0546             bool valueFound = false;
0547             for (auto &p : paramNames) {
0548                 const QString val = m_asset->get(p.toUtf8().constData());
0549                 if (!val.isEmpty()) {
0550                     valueFound = true;
0551                 }
0552                 values << val;
0553             }
0554             if (!valueFound) {
0555                 return (element.attribute(QStringLiteral("value")).isNull() ? parseAttribute(m_ownerId, QStringLiteral("default"), element)
0556                                                                             : element.attribute(QStringLiteral("value")));
0557             }
0558             return values.join(QLatin1Char('\n'));
0559         }
0560         QString value(m_asset->get(paramName.toUtf8().constData()));
0561         if (value.isEmpty()) {
0562             if (element.hasAttribute(QStringLiteral("default"))) {
0563                 value = parseAttribute(m_ownerId, QStringLiteral("default"), element).toString();
0564             } else {
0565                 value = element.attribute(QStringLiteral("value"));
0566             }
0567         }
0568         return value;
0569     }
0570     case ListValuesRole:
0571         return element.attribute(QStringLiteral("paramlist")).split(QLatin1Char(';'));
0572     case ListNamesRole: {
0573         QDomElement namesElem = element.firstChildElement(QStringLiteral("paramlistdisplay"));
0574         return i18n(namesElem.text().toUtf8().data()).split(QLatin1Char(','));
0575     }
0576     case ListDependenciesRole: {
0577         QDomNodeList dependencies = element.elementsByTagName(QStringLiteral("paramdependencies"));
0578         if (!dependencies.isEmpty()) {
0579             QDomDocument doc;
0580             QDomElement d = doc.createElement(QStringLiteral("deps"));
0581             doc.appendChild(d);
0582             for (int i = 0; i < dependencies.count(); i++) {
0583                 d.appendChild(doc.importNode(dependencies.at(i), true));
0584             }
0585             return doc.toString();
0586         }
0587         return QVariant();
0588     }
0589     case NewStuffRole:
0590         return element.attribute(QStringLiteral("newstuff"));
0591     case ModeRole:
0592         return element.attribute(QStringLiteral("mode"));
0593     case List1Role:
0594         return parseAttribute(m_ownerId, QStringLiteral("list1"), element);
0595     case List2Role:
0596         return parseAttribute(m_ownerId, QStringLiteral("list2"), element);
0597     case Enum1Role:
0598         return m_asset->get_double("1");
0599     case Enum2Role:
0600         return m_asset->get_double("2");
0601     case Enum3Role:
0602         return m_asset->get_double("3");
0603     case Enum4Role:
0604         return m_asset->get_double("4");
0605     case Enum5Role:
0606         return m_asset->get_double("5");
0607     case Enum6Role:
0608         return m_asset->get_double("6");
0609     case Enum7Role:
0610         return m_asset->get_double("7");
0611     case Enum8Role:
0612         return m_asset->get_double("8");
0613     case Enum9Role:
0614         return m_asset->get_double("9");
0615     case Enum10Role:
0616         return m_asset->get_double("10");
0617     case Enum11Role:
0618         return m_asset->get_double("11");
0619     case Enum12Role:
0620         return m_asset->get_double("12");
0621     case Enum13Role:
0622         return m_asset->get_double("13");
0623     case Enum14Role:
0624         return m_asset->get_double("14");
0625     case Enum15Role:
0626         return m_asset->get_double("15");
0627     }
0628     return QVariant();
0629 }
0630 
0631 const QString AssetParameterModel::framesToTime(int t) const
0632 {
0633     return m_asset->frames_to_time(t, mlt_time_clock);
0634 }
0635 
0636 int AssetParameterModel::rowCount(const QModelIndex &parent) const
0637 {
0638     // qDebug() << "===================================================== Requested rowCount" << parent << m_rows.size();
0639     if (parent.isValid()) return 0;
0640     return m_rows.size();
0641 }
0642 
0643 // static
0644 ParamType AssetParameterModel::paramTypeFromStr(const QString &type)
0645 {
0646     if (type == QLatin1String("double") || type == QLatin1String("float") || type == QLatin1String("constant")) {
0647         return ParamType::Double;
0648     }
0649     if (type == QLatin1String("list")) {
0650         return ParamType::List;
0651     }
0652     if (type == QLatin1String("listdependency")) {
0653         return ParamType::ListWithDependency;
0654     }
0655     if (type == QLatin1String("urllist")) {
0656         return ParamType::UrlList;
0657     }
0658     if (type == QLatin1String("bool")) {
0659         return ParamType::Bool;
0660     }
0661     if (type == QLatin1String("switch")) {
0662         return ParamType::Switch;
0663     }
0664     if (type == QLatin1String("multiswitch")) {
0665         return ParamType::MultiSwitch;
0666     } else if (type == QLatin1String("simplekeyframe")) {
0667         return ParamType::KeyframeParam;
0668     } else if (type == QLatin1String("animatedrect") || type == QLatin1String("rect")) {
0669         return ParamType::AnimatedRect;
0670     } else if (type == QLatin1String("geometry")) {
0671         return ParamType::Geometry;
0672     } else if (type == QLatin1String("keyframe") || type == QLatin1String("animated")) {
0673         return ParamType::KeyframeParam;
0674     } else if (type == QLatin1String("color")) {
0675         return ParamType::Color;
0676     } else if (type == QLatin1String("fixedcolor")) {
0677         return ParamType::FixedColor;
0678     } else if (type == QLatin1String("colorwheel")) {
0679         return ParamType::ColorWheel;
0680     } else if (type == QLatin1String("position")) {
0681         return ParamType::Position;
0682     } else if (type == QLatin1String("curve")) {
0683         return ParamType::Curve;
0684     } else if (type == QLatin1String("bezier_spline")) {
0685         return ParamType::Bezier_spline;
0686     } else if (type == QLatin1String("roto-spline")) {
0687         return ParamType::Roto_spline;
0688     } else if (type == QLatin1String("wipe")) {
0689         return ParamType::Wipe;
0690     } else if (type == QLatin1String("url")) {
0691         return ParamType::Url;
0692     } else if (type == QLatin1String("keywords")) {
0693         return ParamType::Keywords;
0694     } else if (type == QLatin1String("fontfamily")) {
0695         return ParamType::Fontfamily;
0696     } else if (type == QLatin1String("filterjob")) {
0697         return ParamType::Filterjob;
0698     } else if (type == QLatin1String("readonly")) {
0699         return ParamType::Readonly;
0700     } else if (type == QLatin1String("hidden")) {
0701         return ParamType::Hidden;
0702     }
0703     qDebug() << "WARNING: Unknown type :" << type;
0704     return ParamType::Double;
0705 }
0706 
0707 // static
0708 bool AssetParameterModel::isAnimated(ParamType type)
0709 {
0710     return type == ParamType::KeyframeParam || type == ParamType::AnimatedRect || type == ParamType::ColorWheel || type == ParamType::Roto_spline ||
0711            type == ParamType::Color;
0712 }
0713 
0714 // static
0715 QString AssetParameterModel::getDefaultKeyframes(int start, const QString &defaultValue, bool linearOnly)
0716 {
0717     QString keyframes = QString::number(start);
0718     if (linearOnly) {
0719         keyframes.append(QLatin1Char('='));
0720     } else {
0721         switch (KdenliveSettings::defaultkeyframeinterp()) {
0722         case mlt_keyframe_discrete:
0723             keyframes.append(QStringLiteral("|="));
0724             break;
0725         case mlt_keyframe_smooth:
0726             keyframes.append(QStringLiteral("~="));
0727             break;
0728         default:
0729             keyframes.append(QLatin1Char('='));
0730             break;
0731         }
0732     }
0733     keyframes.append(defaultValue);
0734     return keyframes;
0735 }
0736 
0737 QVariant AssetParameterModel::parseAttribute(const ObjectId &owner, const QString &attribute, const QDomElement &element, QVariant defaultValue) const
0738 {
0739     if (!element.hasAttribute(attribute) && !defaultValue.isNull()) {
0740         return defaultValue;
0741     }
0742     ParamType type = paramTypeFromStr(element.attribute(QStringLiteral("type")));
0743     QString content = element.attribute(attribute);
0744     if (type == ParamType::UrlList && attribute == QLatin1String("default")) {
0745         QString values = element.attribute(QStringLiteral("paramlist"));
0746         if (values == QLatin1String("%lutPaths")) {
0747             QString filter = element.attribute(QStringLiteral("filter"));
0748             ;
0749             filter.remove(0, filter.indexOf(QLatin1String("(")) + 1);
0750             filter.remove(filter.indexOf(QLatin1String(")")) - 1, -1);
0751             QStringList fileExt = filter.split(QStringLiteral(" "));
0752             // check for Kdenlive installed luts files
0753             QStringList customLuts = QStandardPaths::locateAll(QStandardPaths::AppLocalDataLocation, QStringLiteral("luts"), QStandardPaths::LocateDirectory);
0754             QStringList results;
0755             for (const QString &folderpath : qAsConst(customLuts)) {
0756                 QDir dir(folderpath);
0757                 QDirIterator it(dir.absolutePath(), fileExt, QDir::Files, QDirIterator::Subdirectories);
0758                 while (it.hasNext()) {
0759                     results.append(it.next());
0760                     break;
0761                 }
0762             }
0763             if (!results.isEmpty()) {
0764                 return results.first();
0765             }
0766             return defaultValue;
0767         }
0768     }
0769     std::unique_ptr<ProfileModel> &profile = pCore->getCurrentProfile();
0770     int width = profile->width();
0771     int height = profile->height();
0772     QSize frameSize = pCore->getItemFrameSize(owner);
0773     if (type == ParamType::AnimatedRect && content == "adjustcenter" && !frameSize.isEmpty()) {
0774         int contentHeight = height;
0775         int contentWidth = width;
0776         double sourceDar = frameSize.width() / frameSize.height();
0777         if (sourceDar > pCore->getCurrentDar()) {
0778             // Fit to width
0779             double factor = double(width) / frameSize.width() * pCore->getCurrentSar();
0780             contentHeight = qRound(height * factor);
0781         } else {
0782             // Fit to height
0783             double factor = double(height) / frameSize.height();
0784             contentWidth = qRound(frameSize.width() / pCore->getCurrentSar() * factor);
0785         }
0786         // Center
0787         content = QString("%1 %2 %3 %4").arg((width - contentWidth) / 2).arg((height - contentHeight) / 2).arg(contentWidth).arg(contentHeight);
0788     } else if (content.contains(QLatin1Char('%'))) {
0789         int in = pCore->getItemIn(owner);
0790         int out = in + pCore->getItemDuration(owner) - 1;
0791         if (m_ownerId.type == KdenliveObjectType::TimelineComposition && out == -1) {
0792             out = m_asset->get_int("out");
0793         }
0794         int currentPos = 0;
0795         if (content.contains(QLatin1String("%position"))) {
0796             // Calculate playhead position relative to clip
0797             int playhead = pCore->getMonitorPosition(m_ownerId.type == KdenliveObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor);
0798             int itemPosition = pCore->getItemPosition(m_ownerId);
0799             int itemIn = pCore->getItemIn(m_ownerId);
0800             currentPos = playhead - itemPosition + itemIn;
0801             currentPos = qBound(itemIn, currentPos, itemIn + pCore->getItemDuration(m_ownerId) - 1);
0802         }
0803         int frame_duration = pCore->getDurationFromString(KdenliveSettings::fade_duration());
0804         double fitScale = qMin(double(width) / double(frameSize.width()), double(height) / double(frameSize.height()));
0805         // replace symbols in the double parameter
0806         content.replace(QLatin1String("%maxWidth"), QString::number(width))
0807             .replace(QLatin1String("%maxHeight"), QString::number(height))
0808             .replace(QLatin1String("%width"), QString::number(width))
0809             .replace(QLatin1String("%height"), QString::number(height))
0810             .replace(QLatin1String("%position"), QString::number(currentPos))
0811             .replace(QLatin1String("%contentWidth"), QString::number(frameSize.width()))
0812             .replace(QLatin1String("%contentHeight"), QString::number(frameSize.height()))
0813             .replace(QLatin1String("%fittedContentWidth"), QString::number(frameSize.width() * fitScale))
0814             .replace(QLatin1String("%fittedContentHeight"), QString::number(frameSize.height() * fitScale))
0815             .replace(QLatin1String("%out"), QString::number(out))
0816             .replace(QLatin1String("%fade"), QString::number(frame_duration));
0817         if ((type == ParamType::AnimatedRect || type == ParamType::Geometry) && attribute == QLatin1String("default")) {
0818             if (content.contains(QLatin1Char('%'))) {
0819                 // This is a generic default like: "25% 0% 50% 100%". Parse values
0820                 QStringList numbers = content.split(QLatin1Char(' '));
0821                 content.clear();
0822                 int ix = 0;
0823                 for (QString &val : numbers) {
0824                     if (val.endsWith(QLatin1Char('%'))) {
0825                         val.chop(1);
0826                         double n = val.toDouble() / 100.;
0827                         if (ix % 2 == 0) {
0828                             n *= width;
0829                         } else {
0830                             n *= height;
0831                         }
0832                         ix++;
0833                         content.append(QString("%1 ").arg(qRound(n)));
0834                     } else {
0835                         content.append(QString("%1 ").arg(val));
0836                     }
0837                 }
0838                 content = content.trimmed();
0839             }
0840         } else if (type == ParamType::Double || type == ParamType::Hidden) {
0841             // Use a Mlt::Properties to parse mathematical operators
0842             bool ok;
0843             double result = content.toDouble(&ok);
0844             if (ok) {
0845                 return result;
0846             }
0847             Mlt::Properties p;
0848             p.set("eval", content.prepend(QLatin1Char('@')).toLatin1().constData());
0849             return p.get_double("eval");
0850         }
0851     } else if (type == ParamType::Double || type == ParamType::Hidden) {
0852         if (attribute == QLatin1String("default")) {
0853             if (content.isEmpty()) {
0854                 return QVariant();
0855             }
0856             return content.toDouble();
0857         }
0858         bool ok;
0859         double converted = content.toDouble(&ok);
0860         if (!ok) {
0861             qDebug() << "QLocale: Could not load double parameter" << content;
0862         }
0863         return converted;
0864     }
0865     if (attribute == QLatin1String("default")) {
0866         if (type == ParamType::KeyframeParam) {
0867             if (!content.contains(QLatin1Char(';'))) {
0868                 return content.toDouble();
0869             }
0870         } else if (type == ParamType::List) {
0871             bool ok;
0872             double res = content.toDouble(&ok);
0873             if (ok) {
0874                 return res;
0875             }
0876             return defaultValue.isNull() ? content : defaultValue;
0877         }
0878     }
0879     return content;
0880 }
0881 
0882 QVariant AssetParameterModel::parseSubAttributes(const QString &attribute, const QDomElement &element) const
0883 {
0884     QDomNodeList nodeList = element.elementsByTagName(attribute);
0885     if (nodeList.isEmpty()) {
0886         return QVariant();
0887     }
0888     QVariantList jobDataList;
0889     for (int i = 0; i < nodeList.count(); ++i) {
0890         QDomElement currentParameter = nodeList.item(i).toElement();
0891         QStringList jobData{currentParameter.attribute(QStringLiteral("name")), currentParameter.text()};
0892         jobDataList << jobData;
0893     }
0894     return jobDataList;
0895 }
0896 
0897 QString AssetParameterModel::getAssetId() const
0898 {
0899     return m_assetId;
0900 }
0901 
0902 const QString AssetParameterModel::getAssetMltId()
0903 {
0904     return m_asset->get("id");
0905 }
0906 
0907 void AssetParameterModel::setActive(bool active)
0908 {
0909     m_active = active;
0910 }
0911 
0912 bool AssetParameterModel::isActive() const
0913 {
0914     return m_active;
0915 }
0916 
0917 QVector<QPair<QString, QVariant>> AssetParameterModel::getAllParameters() const
0918 {
0919     QVector<QPair<QString, QVariant>> res;
0920     res.reserve(int(m_fixedParams.size() + m_params.size()));
0921     for (const auto &fixed : m_fixedParams) {
0922         res.push_back(QPair<QString, QVariant>(fixed.first, fixed.second));
0923     }
0924 
0925     for (const auto &param : m_params) {
0926         if (!param.first.isEmpty()) {
0927             QModelIndex ix = index(m_rows.indexOf(param.first), 0);
0928             if (m_params.at(param.first).type == ParamType::MultiSwitch) {
0929                 // Multiswitch param value is not updated on change, fo fetch real value now
0930                 QVariant multiVal = data(ix, AssetParameterModel::ValueRole).toString();
0931                 res.push_back(QPair<QString, QVariant>(param.first, multiVal));
0932                 continue;
0933             } else if (m_params.at(param.first).type == ParamType::Position) {
0934                 bool relative = data(ix, AssetParameterModel::RelativePosRole).toBool();
0935                 if (!relative) {
0936                     int in = pCore->getItemIn(m_ownerId);
0937                     int val = param.second.value.toInt();
0938                     res.push_back(QPair<QString, QVariant>(param.first, QVariant(val - in)));
0939                     continue;
0940                 }
0941             }
0942             res.push_back(QPair<QString, QVariant>(param.first, param.second.value));
0943         }
0944     }
0945     return res;
0946 }
0947 
0948 QJsonDocument AssetParameterModel::toJson(QVector<int> selection, bool includeFixed) const
0949 {
0950     QJsonArray list;
0951     if (selection == (QVector<int>() << -1)) {
0952         selection.clear();
0953     }
0954     if (includeFixed) {
0955         for (const auto &fixed : m_fixedParams) {
0956             QJsonObject currentParam;
0957             QModelIndex ix = index(m_rows.indexOf(fixed.first), 0);
0958             currentParam.insert(QLatin1String("name"), QJsonValue(fixed.first));
0959             currentParam.insert(QLatin1String("value"),
0960 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0961                                 fixed.second.type() == QVariant::Double
0962 #else
0963                                 fixed.second.typeId() == QMetaType::Double
0964 #endif
0965                                     ? QJsonValue(fixed.second.toDouble())
0966                                     : QJsonValue(fixed.second.toString()));
0967             int type = data(ix, AssetParameterModel::TypeRole).toInt();
0968             double min = data(ix, AssetParameterModel::MinRole).toDouble();
0969             double max = data(ix, AssetParameterModel::MaxRole).toDouble();
0970             double factor = data(ix, AssetParameterModel::FactorRole).toDouble();
0971             int in = data(ix, AssetParameterModel::ParentInRole).toInt();
0972             int out = in + data(ix, AssetParameterModel::ParentDurationRole).toInt();
0973             if (factor > 0) {
0974                 min /= factor;
0975                 max /= factor;
0976             }
0977             currentParam.insert(QLatin1String("type"), QJsonValue(type));
0978             currentParam.insert(QLatin1String("min"), QJsonValue(min));
0979             currentParam.insert(QLatin1String("max"), QJsonValue(max));
0980             currentParam.insert(QLatin1String("in"), QJsonValue(in));
0981             currentParam.insert(QLatin1String("out"), QJsonValue(out));
0982             list.push_back(currentParam);
0983         }
0984     }
0985 
0986     QString x, y, w, h;
0987     int rectIn = 0, rectOut = 0;
0988     for (const auto &param : m_params) {
0989         if (!includeFixed && !isAnimated(param.second.type)) {
0990             continue;
0991         }
0992         QJsonObject currentParam;
0993         QModelIndex ix = index(m_rows.indexOf(param.first), 0);
0994 
0995         if (param.first.contains(QLatin1String("Position X"))) {
0996             x = param.second.value.toString();
0997             rectIn = data(ix, AssetParameterModel::ParentInRole).toInt();
0998             rectOut = rectIn + data(ix, AssetParameterModel::ParentDurationRole).toInt();
0999         }
1000         if (param.first.contains(QLatin1String("Position Y"))) {
1001             y = param.second.value.toString();
1002         }
1003         if (param.first.contains(QLatin1String("Size X"))) {
1004             w = param.second.value.toString();
1005         }
1006         if (param.first.contains(QLatin1String("Size Y"))) {
1007             h = param.second.value.toString();
1008         }
1009 
1010         currentParam.insert(QLatin1String("name"), QJsonValue(param.first));
1011         currentParam.insert(QLatin1String("DisplayName"), QJsonValue(param.second.name));
1012         if (
1013 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1014             param.second.value.type() == QVariant::Double
1015 
1016 #else
1017             param.second.value.typeId() == QMetaType::Double
1018 #endif
1019         ) {
1020             currentParam.insert(QLatin1String("value"), QJsonValue(param.second.value.toDouble()));
1021         } else {
1022             QString resultValue = param.second.value.toString();
1023             if (selection.isEmpty()) {
1024                 currentParam.insert(QLatin1String("value"), QJsonValue(resultValue));
1025             } else {
1026                 // Filter out unwanted keyframes
1027                 if (param.second.type == ParamType::Roto_spline) {
1028                     QJsonParseError jsonError;
1029                     QJsonDocument doc = QJsonDocument::fromJson(resultValue.toUtf8(), &jsonError);
1030                     QVariant data = doc.toVariant();
1031                     if (data.canConvert<QVariantMap>()) {
1032                         QMap<QString, QVariant> map = data.toMap();
1033                         QMap<QString, QVariant>::const_iterator i = map.constBegin();
1034                         QJsonObject dataObject;
1035                         int ix = 0;
1036                         while (i != map.constEnd()) {
1037                             if (selection.contains(ix)) {
1038                                 dataObject.insert(i.key(), i.value().toJsonValue());
1039                             }
1040                             ix++;
1041                             ++i;
1042                         }
1043                         currentParam.insert(QLatin1String("value"), QJsonValue(dataObject));
1044                     }
1045                 } else {
1046                     QStringList values = resultValue.split(QLatin1Char(';'));
1047                     QStringList remainingValues;
1048                     int ix = 0;
1049                     for (auto &val : values) {
1050                         if (selection.contains(ix)) {
1051                             remainingValues << val;
1052                         }
1053                         ix++;
1054                     }
1055                     currentParam.insert(QLatin1String("value"), QJsonValue(remainingValues.join(QLatin1Char(';'))));
1056                 }
1057             }
1058         }
1059         int type = data(ix, AssetParameterModel::TypeRole).toInt();
1060         double min = data(ix, AssetParameterModel::MinRole).toDouble();
1061         double max = data(ix, AssetParameterModel::MaxRole).toDouble();
1062         double factor = data(ix, AssetParameterModel::FactorRole).toDouble();
1063         int in = data(ix, AssetParameterModel::ParentInRole).toInt();
1064         int out = in + data(ix, AssetParameterModel::ParentDurationRole).toInt();
1065         bool opacity = data(ix, AssetParameterModel::OpacityRole).toBool();
1066         if (factor > 0) {
1067             min /= factor;
1068             max /= factor;
1069         }
1070         currentParam.insert(QLatin1String("type"), QJsonValue(type));
1071         currentParam.insert(QLatin1String("min"), QJsonValue(min));
1072         currentParam.insert(QLatin1String("max"), QJsonValue(max));
1073         currentParam.insert(QLatin1String("in"), QJsonValue(in));
1074         currentParam.insert(QLatin1String("out"), QJsonValue(out));
1075         currentParam.insert(QLatin1String("opacity"), QJsonValue(opacity));
1076         list.push_back(currentParam);
1077     }
1078     if (!(x.isEmpty() || y.isEmpty() || w.isEmpty() || h.isEmpty())) {
1079         QJsonObject currentParam;
1080         currentParam.insert(QLatin1String("name"), QStringLiteral("rect"));
1081         int size = x.split(";").length();
1082         QString value;
1083         for (int i = 0; i < size; i++) {
1084             if (!selection.isEmpty() && !selection.contains(i)) {
1085                 continue;
1086             }
1087             QSize frameSize = pCore->getCurrentFrameSize();
1088             QString pos = x.split(";").at(i).split("=").at(0);
1089             double xval = x.split(";").at(i).split("=").at(1).toDouble();
1090             xval = xval * frameSize.width();
1091             double yval = y.split(";").at(i).split("=").at(1).toDouble();
1092             yval = yval * frameSize.height();
1093             double wval = w.split(";").at(i).split("=").at(1).toDouble();
1094             wval = wval * frameSize.width() * 2;
1095             double hval = h.split(";").at(i).split("=").at(1).toDouble();
1096             hval = hval * frameSize.height() * 2;
1097             value.append(QString("%1=%2 %3 %4 %5 1;").arg(pos).arg(int(xval - wval / 2)).arg(int(yval - hval / 2)).arg(int(wval)).arg(int(hval)));
1098         }
1099         currentParam.insert(QLatin1String("value"), value);
1100         currentParam.insert(QLatin1String("type"), QJsonValue(int(ParamType::AnimatedRect)));
1101         currentParam.insert(QLatin1String("min"), QJsonValue(0));
1102         currentParam.insert(QLatin1String("max"), QJsonValue(0));
1103         currentParam.insert(QLatin1String("in"), QJsonValue(rectIn));
1104         currentParam.insert(QLatin1String("out"), QJsonValue(rectOut));
1105         list.push_front(currentParam);
1106     }
1107     return QJsonDocument(list);
1108 }
1109 
1110 QJsonDocument AssetParameterModel::valueAsJson(int pos, bool includeFixed) const
1111 {
1112     QJsonArray list;
1113     if (!m_keyframes) {
1114         return QJsonDocument(list);
1115     }
1116 
1117     if (includeFixed) {
1118         for (const auto &fixed : m_fixedParams) {
1119             QJsonObject currentParam;
1120             QModelIndex ix = index(m_rows.indexOf(fixed.first), 0);
1121             auto value = m_keyframes->getInterpolatedValue(pos, ix);
1122             currentParam.insert(QLatin1String("name"), QJsonValue(fixed.first));
1123             currentParam.insert(QLatin1String("value"), QJsonValue(QStringLiteral("0=%1").arg(
1124 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1125 
1126                                                             value.type() == QVariant::Double
1127 #else
1128                                                             value.typeId() == QMetaType::Double
1129 #endif
1130                                                                 ? QString::number(value.toDouble())
1131                                                                 : value.toString())));
1132             int type = data(ix, AssetParameterModel::TypeRole).toInt();
1133             double min = data(ix, AssetParameterModel::MinRole).toDouble();
1134             double max = data(ix, AssetParameterModel::MaxRole).toDouble();
1135             double factor = data(ix, AssetParameterModel::FactorRole).toDouble();
1136             if (factor > 0) {
1137                 min /= factor;
1138                 max /= factor;
1139             }
1140             currentParam.insert(QLatin1String("type"), QJsonValue(type));
1141             currentParam.insert(QLatin1String("min"), QJsonValue(min));
1142             currentParam.insert(QLatin1String("max"), QJsonValue(max));
1143             currentParam.insert(QLatin1String("in"), QJsonValue(0));
1144             currentParam.insert(QLatin1String("out"), QJsonValue(0));
1145             list.push_back(currentParam);
1146         }
1147     }
1148 
1149     double x, y, w, h;
1150     int count = 0;
1151     for (const auto &param : m_params) {
1152         if (!includeFixed && !isAnimated(param.second.type)) {
1153             continue;
1154         }
1155         QJsonObject currentParam;
1156         QModelIndex ix = index(m_rows.indexOf(param.first), 0);
1157         auto value = m_keyframes->getInterpolatedValue(pos, ix);
1158 
1159         if (param.first.contains("Position X")) {
1160             x = value.toDouble();
1161             count++;
1162         }
1163         if (param.first.contains("Position Y")) {
1164             y = value.toDouble();
1165             count++;
1166         }
1167         if (param.first.contains("Size X")) {
1168             w = value.toDouble();
1169             count++;
1170         }
1171         if (param.first.contains("Size Y")) {
1172             h = value.toDouble();
1173             count++;
1174         }
1175 
1176         currentParam.insert(QLatin1String("name"), QJsonValue(param.first));
1177         QString stringValue;
1178         if (param.second.type == ParamType::Roto_spline) {
1179             QJsonObject obj;
1180             obj.insert(QStringLiteral("0"), value.toJsonArray());
1181             currentParam.insert(QLatin1String("value"), QJsonValue(obj));
1182         } else {
1183             stringValue = QStringLiteral("0=%1").arg(
1184 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1185                 value.type() == QVariant::Double
1186 
1187 #else
1188                 value.typeId() == QMetaType::Double
1189 #endif
1190                     ? QString::number(value.toDouble())
1191                     : value.toString());
1192             currentParam.insert(QLatin1String("value"), QJsonValue(stringValue));
1193         }
1194 
1195         int type = data(ix, AssetParameterModel::TypeRole).toInt();
1196         double min = data(ix, AssetParameterModel::MinRole).toDouble();
1197         double max = data(ix, AssetParameterModel::MaxRole).toDouble();
1198         double factor = data(ix, AssetParameterModel::FactorRole).toDouble();
1199         if (factor > 0) {
1200             min /= factor;
1201             max /= factor;
1202         }
1203         currentParam.insert(QLatin1String("type"), QJsonValue(type));
1204         currentParam.insert(QLatin1String("min"), QJsonValue(min));
1205         currentParam.insert(QLatin1String("max"), QJsonValue(max));
1206         currentParam.insert(QLatin1String("in"), QJsonValue(0));
1207         currentParam.insert(QLatin1String("out"), QJsonValue(0));
1208         list.push_back(currentParam);
1209     }
1210 
1211     if (count == 4) {
1212         QJsonObject currentParam;
1213         currentParam.insert(QLatin1String("name"), QStringLiteral("rect"));
1214         QSize frameSize = pCore->getCurrentFrameSize();
1215         x = x * frameSize.width();
1216         y = y * frameSize.height();
1217         w = w * frameSize.width() * 2;
1218         h = h * frameSize.height() * 2;
1219         currentParam.insert(QLatin1String("value"), QString("0=%1 %2 %3 %4 1;").arg(int(x - x / 2)).arg(int(y - y / 2)).arg(int(w)).arg(int(h)));
1220         currentParam.insert(QLatin1String("type"), QJsonValue(int(ParamType::AnimatedRect)));
1221         currentParam.insert(QLatin1String("min"), QJsonValue(0));
1222         currentParam.insert(QLatin1String("max"), QJsonValue(0));
1223         currentParam.insert(QLatin1String("in"), 0);
1224         currentParam.insert(QLatin1String("out"), 0);
1225         list.push_front(currentParam);
1226     }
1227     return QJsonDocument(list);
1228 }
1229 
1230 void AssetParameterModel::deletePreset(const QString &presetFile, const QString &presetName)
1231 {
1232     QJsonObject object;
1233     QJsonArray array;
1234     QFile loadFile(presetFile);
1235     if (loadFile.exists()) {
1236         if (loadFile.open(QIODevice::ReadOnly)) {
1237             QByteArray saveData = loadFile.readAll();
1238             QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
1239             if (loadDoc.isArray()) {
1240                 array = loadDoc.array();
1241                 QList<int> toDelete;
1242                 for (int i = 0; i < array.size(); i++) {
1243                     QJsonValue val = array.at(i);
1244                     if (val.isObject() && val.toObject().keys().contains(presetName)) {
1245                         toDelete << i;
1246                     }
1247                 }
1248                 for (int i : qAsConst(toDelete)) {
1249                     array.removeAt(i);
1250                 }
1251             } else if (loadDoc.isObject()) {
1252                 QJsonObject obj = loadDoc.object();
1253                 qDebug() << " * * ** JSON IS AN OBJECT, DELETING: " << presetName;
1254                 if (obj.keys().contains(presetName)) {
1255                     obj.remove(presetName);
1256                 } else {
1257                     qDebug() << " * * ** JSON DOES NOT CONTAIN: " << obj.keys();
1258                 }
1259                 array.append(obj);
1260             }
1261             loadFile.close();
1262         }
1263     }
1264     if (!loadFile.open(QIODevice::WriteOnly)) {
1265         pCore->displayMessage(i18n("Cannot open preset file %1", presetFile), ErrorMessage);
1266         return;
1267     }
1268     if (array.isEmpty()) {
1269         QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
1270         if (dir.exists(presetFile)) {
1271             // Ensure we don't delete an unwanted file
1272             loadFile.remove();
1273         }
1274     } else {
1275         loadFile.write(QJsonDocument(array).toJson());
1276     }
1277 }
1278 
1279 void AssetParameterModel::savePreset(const QString &presetFile, const QString &presetName)
1280 {
1281     QJsonObject object;
1282     QJsonArray array;
1283     QJsonDocument doc = toJson();
1284     QFile loadFile(presetFile);
1285     if (loadFile.exists()) {
1286         if (loadFile.open(QIODevice::ReadOnly)) {
1287             QByteArray saveData = loadFile.readAll();
1288             QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
1289             if (loadDoc.isArray()) {
1290                 array = loadDoc.array();
1291                 QList<int> toDelete;
1292                 for (int i = 0; i < array.size(); i++) {
1293                     QJsonValue val = array.at(i);
1294                     if (val.isObject() && val.toObject().keys().contains(presetName)) {
1295                         toDelete << i;
1296                     }
1297                 }
1298                 for (int i : qAsConst(toDelete)) {
1299                     array.removeAt(i);
1300                 }
1301             } else if (loadDoc.isObject()) {
1302                 QJsonObject obj = loadDoc.object();
1303                 if (obj.keys().contains(presetName)) {
1304                     obj.remove(presetName);
1305                 }
1306                 array.append(obj);
1307             }
1308             loadFile.close();
1309         }
1310     }
1311     if (!loadFile.open(QIODevice::WriteOnly)) {
1312         pCore->displayMessage(i18n("Cannot open preset file %1", presetFile), ErrorMessage);
1313         return;
1314     }
1315     object[presetName] = doc.array();
1316     array.append(object);
1317     loadFile.write(QJsonDocument(array).toJson());
1318 }
1319 
1320 const QStringList AssetParameterModel::getPresetList(const QString &presetFile) const
1321 {
1322     QFile loadFile(presetFile);
1323     if (!(loadFile.exists() && loadFile.open(QIODevice::ReadOnly))) {
1324         // could not open file
1325         return QStringList();
1326     }
1327     QByteArray saveData = loadFile.readAll();
1328     QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
1329     if (loadDoc.isObject()) {
1330         qDebug() << "// PRESET LIST IS AN OBJECT!!!";
1331         return loadDoc.object().keys();
1332     }
1333 
1334     QStringList result;
1335     if (loadDoc.isArray()) {
1336         qDebug() << "// PRESET LIST IS AN ARRAY!!!";
1337 
1338         QJsonArray array = loadDoc.array();
1339         for (auto &&i : array) {
1340             QJsonValue val = i;
1341             if (val.isObject()) {
1342                 result << val.toObject().keys();
1343             }
1344         }
1345     }
1346     return result;
1347 }
1348 
1349 const QVector<QPair<QString, QVariant>> AssetParameterModel::loadPreset(const QString &presetFile, const QString &presetName)
1350 {
1351     QFile loadFile(presetFile);
1352     QVector<QPair<QString, QVariant>> params;
1353     if (loadFile.exists() && loadFile.open(QIODevice::ReadOnly)) {
1354         QByteArray saveData = loadFile.readAll();
1355         QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
1356         if (loadDoc.isObject() && loadDoc.object().contains(presetName)) {
1357             qDebug() << "..........\n..........\nLOADING OBJECT JSON";
1358             QJsonValue val = loadDoc.object().value(presetName);
1359             if (val.isObject()) {
1360                 QVariantMap map = val.toObject().toVariantMap();
1361                 QMap<QString, QVariant>::const_iterator i = map.constBegin();
1362                 while (i != map.constEnd()) {
1363                     params.append({i.key(), i.value()});
1364                     ++i;
1365                 }
1366             }
1367         } else if (loadDoc.isArray()) {
1368             QJsonArray array = loadDoc.array();
1369             for (auto &&i : array) {
1370                 QJsonValue val = i;
1371                 if (val.isObject() && val.toObject().contains(presetName)) {
1372                     QJsonValue preset = val.toObject().value(presetName);
1373                     if (preset.isArray()) {
1374                         QJsonArray paramArray = preset.toArray();
1375                         for (auto &&j : paramArray) {
1376                             QJsonValue v1 = j;
1377                             if (v1.isObject()) {
1378                                 QJsonObject ob = v1.toObject();
1379                                 params.append({ob.value("name").toString(), ob.value("value").toVariant()});
1380                             }
1381                         }
1382                     }
1383                     qDebug() << "// LOADED PRESET: " << presetName << "\n" << params;
1384                     break;
1385                 }
1386             }
1387         }
1388     }
1389     return params;
1390 }
1391 
1392 void AssetParameterModel::setParametersFromTask(const paramVector &params)
1393 {
1394     if (m_keyframes) {
1395         // We have keyframable parameters. Ensure all of these share the same keyframes,
1396         // required by Kdenlive's current implementation
1397         m_keyframes->setParametersFromTask(params);
1398     } else {
1399         // Setting no keyframable param
1400         paramVector previousParams;
1401         for (auto p : params) {
1402             previousParams.append({p.first, getParamFromName(p.first)});
1403         }
1404         Fun redo = [this, params]() {
1405             setParameters(params);
1406             return true;
1407         };
1408         Fun undo = [this, previousParams]() {
1409             setParameters(previousParams);
1410             return true;
1411         };
1412         redo();
1413         pCore->pushUndo(undo, redo, i18n("Update effect"));
1414     }
1415 }
1416 
1417 void AssetParameterModel::setParameters(const paramVector &params, bool update)
1418 {
1419     KdenliveObjectType itemType;
1420     if (!update) {
1421         // Change itemId to NoItem to ensure we don't send any update like refreshProjectItem that would trigger monitor refreshes.
1422         itemType = m_ownerId.type;
1423         m_ownerId.type = KdenliveObjectType::NoItem;
1424     }
1425     for (const auto &param : params) {
1426         QModelIndex ix = index(m_rows.indexOf(param.first), 0);
1427         setParameter(param.first, param.second.toString(), false, ix);
1428         if (m_keyframes) {
1429             KeyframeModel *km = m_keyframes->getKeyModel(ix);
1430             if (km) {
1431                 km->refresh();
1432             }
1433         }
1434     }
1435     if (!update) {
1436         // restore itemType
1437         m_ownerId.type = itemType;
1438     }
1439     Q_EMIT dataChanged(index(0), index(m_rows.count()), {});
1440 }
1441 
1442 ObjectId AssetParameterModel::getOwnerId() const
1443 {
1444     return m_ownerId;
1445 }
1446 
1447 void AssetParameterModel::addKeyframeParam(const QModelIndex &index, int in, int out)
1448 {
1449     if (m_keyframes) {
1450         m_keyframes->addParameter(index, in, out);
1451     } else {
1452         m_keyframes.reset(new KeyframeModelList(shared_from_this(), index, pCore->undoStack(), in, out));
1453     }
1454 }
1455 
1456 std::shared_ptr<KeyframeModelList> AssetParameterModel::getKeyframeModel()
1457 {
1458     return m_keyframes;
1459 }
1460 
1461 void AssetParameterModel::resetAsset(std::unique_ptr<Mlt::Properties> asset)
1462 {
1463     m_asset = std::move(asset);
1464 }
1465 
1466 bool AssetParameterModel::hasMoreThanOneKeyframe() const
1467 {
1468     if (m_keyframes) {
1469         return (!m_keyframes->isEmpty() && !m_keyframes->singleKeyframe());
1470     }
1471     return false;
1472 }
1473 
1474 int AssetParameterModel::time_to_frames(const QString &time) const
1475 {
1476     return m_asset->time_to_frames(time.toUtf8().constData());
1477 }
1478 
1479 void AssetParameterModel::passProperties(Mlt::Properties &target)
1480 {
1481     target.set("_profile", pCore->getProjectProfile().get_profile(), 0);
1482     target.set_lcnumeric(m_asset->get_lcnumeric());
1483 }
1484 
1485 void AssetParameterModel::setProgress(int progress)
1486 {
1487     m_filterProgress = progress;
1488     Q_EMIT dataChanged(index(0, 0), index(m_rows.count() - 1, 0), {AssetParameterModel::FilterProgressRole});
1489 }
1490 
1491 Mlt::Properties *AssetParameterModel::getAsset()
1492 {
1493     return m_asset.get();
1494 }
1495 
1496 const QVariant AssetParameterModel::getParamFromName(const QString &paramName)
1497 {
1498     QModelIndex ix = index(m_rows.indexOf(paramName), 0);
1499     if (ix.isValid()) {
1500         return data(ix, ValueRole);
1501     }
1502     return QVariant();
1503 }
1504 
1505 const QModelIndex AssetParameterModel::getParamIndexFromName(const QString &paramName)
1506 {
1507     return index(m_rows.indexOf(paramName), 0);
1508 }