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 ¶mName) 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 ¶mIndex) 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 ¶mValue, bool update, const QModelIndex ¶mIndex) 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 ¶m : 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 ¶m : 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 ¶m : 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 ¶ms) 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 ¶ms, 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 ¶m : 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 ¶mName) 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 ¶mName) 1506 { 1507 return index(m_rows.indexOf(paramName), 0); 1508 }