File indexing completed on 2024-05-12 08:53:17

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