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