Warning, /multimedia/kdenlive/src/assets/abstractassetsrepository.ipp is written in an unsupported language. File is not indexed.
0001 /* 0002 * SPDX-FileCopyrightText: 2017 Nicolas Carion 0003 * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 0006 #include "xml/xml.hpp" 0007 #include "kdenlivesettings.h" 0008 #include "core.h" 0009 0010 #include <QDir> 0011 #include <QFile> 0012 #include <QStandardPaths> 0013 #include <QString> 0014 #include <QTextStream> 0015 #include <KLocalizedString> 0016 0017 #include <locale> 0018 #ifdef Q_OS_MAC 0019 #include <xlocale.h> 0020 #endif 0021 0022 template <typename AssetType> AbstractAssetsRepository<AssetType>::AbstractAssetsRepository() = default; 0023 0024 template <typename AssetType> void AbstractAssetsRepository<AssetType>::init() 0025 { 0026 // Parse blacklist 0027 parseAssetList(assetBlackListPath(), m_blacklist); 0028 0029 // Parse preferred list 0030 parseAssetList(assetPreferredListPath(), m_preferred_list); 0031 0032 // Retrieve the list of MLT's available assets. 0033 QScopedPointer<Mlt::Properties> assets(retrieveListFromMlt()); 0034 QStringList emptyMetaAssets; 0035 int max = assets->count(); 0036 QString sox = QStringLiteral("sox."); 0037 for (int i = 0; i < max; ++i) { 0038 Info info; 0039 QString name = assets->get_name(i); 0040 info.id = name; 0041 if (name.startsWith(sox)) { 0042 // sox effects are not usage directly (parameters not available) 0043 continue; 0044 } 0045 if (!m_blacklist.contains(name) && parseInfoFromMlt(name, info)) { 0046 m_assets[name] = info; 0047 if (info.xml.isNull()) { 0048 // Metadata was invalid 0049 emptyMetaAssets << name; 0050 } 0051 } else { 0052 if (!m_blacklist.contains(name)) { 0053 qWarning() << "Failed to parse" << name; 0054 } 0055 } 0056 } 0057 0058 // We now parse custom effect xml 0059 0060 // Set the directories to look into for effects. 0061 QStringList asset_dirs = assetDirs(); 0062 0063 /* Parsing of custom xml works as follows: we parse all custom files. 0064 Each of them contains a tag, which is the corresponding mlt asset, and an id that is the name of the asset. Note that several custom files can correspond 0065 to the same tag, and in that case they must have different ids. We do the parsing in a map from ids to parse info, and then we add them to the asset 0066 list, while discarding the bare version of each tag (the one with no file associated) 0067 */ 0068 std::unordered_map<QString, Info> customAssets; 0069 // reverse order to prioritize local install 0070 QListIterator<QString> dirs_it(asset_dirs); 0071 for (dirs_it.toBack(); dirs_it.hasPrevious();) { auto dir=dirs_it.previous(); 0072 QDir current_dir(dir); 0073 QStringList filter {QStringLiteral("*.xml")}; 0074 QStringList fileList = current_dir.entryList(filter, QDir::Files); 0075 for (const auto &file : qAsConst(fileList)) { 0076 QString path = current_dir.absoluteFilePath(file); 0077 parseCustomAssetFile(path, customAssets); 0078 } 0079 } 0080 0081 // We add the custom assets 0082 QStringList missingDependency; 0083 for (const auto &custom : customAssets) { 0084 // Custom assets should override default ones 0085 if (emptyMetaAssets.contains(custom.second.mltId)) { 0086 // We didn't find MLT's metadata for this assed, but have an xml definition, so validate 0087 emptyMetaAssets.removeAll(custom.second.mltId); 0088 } 0089 m_assets[custom.first] = custom.second; 0090 0091 QString dependency = custom.second.xml.attribute(QStringLiteral("dependency"), QString()); 0092 if(!dependency.isEmpty()) { 0093 bool found = false; 0094 QScopedPointer<Mlt::Properties> effects(pCore->getMltRepository()->filters()); 0095 for(int i = 0; i < effects->count(); ++i) { 0096 if(effects->get_name(i) == dependency) { 0097 found = true; 0098 break; 0099 } 0100 } 0101 0102 if(!found) { 0103 QScopedPointer<Mlt::Properties> transitions(pCore->getMltRepository()->transitions()); 0104 for(int i = 0; i < transitions->count(); ++i) { 0105 if(transitions->get_name(i) == dependency) { 0106 found = true; 0107 break; 0108 } 0109 } 0110 } 0111 0112 if(!found) { 0113 // asset depends on another asset that is invalid so remove this asset too 0114 missingDependency << custom.first; 0115 qDebug() << "Asset" << custom.first << "has invalid dependency" << dependency << "and is going to be removed"; 0116 } 0117 } 0118 0119 } 0120 // Remove really invalid assets 0121 emptyMetaAssets << missingDependency; 0122 emptyMetaAssets.removeDuplicates(); 0123 for (const auto &invalid : qAsConst(emptyMetaAssets)) { 0124 m_assets.erase(invalid); 0125 } 0126 } 0127 0128 template <typename AssetType> void AbstractAssetsRepository<AssetType>::parseAssetList(const QString &filePath, QSet<QString> &destination) 0129 { 0130 if (filePath.isEmpty()) 0131 return; 0132 QFile assetFile(filePath); 0133 if (assetFile.open(QIODevice::ReadOnly)) { 0134 QTextStream stream(&assetFile); 0135 QString line; 0136 while (stream.readLineInto(&line)) { 0137 line = line.simplified(); 0138 if (!line.isEmpty() && !line.startsWith('#')) { 0139 destination.insert(line); 0140 } 0141 } 0142 } 0143 } 0144 0145 template <typename AssetType> bool AbstractAssetsRepository<AssetType>::parseInfoFromMlt(const QString &assetId, Info &res) 0146 { 0147 std::unique_ptr<Mlt::Properties> metadata(getMetadata(assetId)); 0148 if (metadata && metadata->is_valid()) { 0149 if (metadata->get("title") && metadata->get("identifier") && strlen(metadata->get("title")) > 0) { 0150 QString id = metadata->get("identifier"); 0151 res.name = i18n(metadata->get("title")); 0152 res.name[0] = res.name[0].toUpper(); 0153 res.description = metadata->get("description") ? i18n(metadata->get("description")) + QString(" (%1)").arg(id) : id; 0154 res.author = metadata->get("creator"); 0155 res.version_str = metadata->get("version"); 0156 res.version = int(ceil(100 * metadata->get_double("version"))); 0157 res.id = res.mltId = assetId; 0158 parseType(metadata.get(), res); 0159 // Create params 0160 QDomDocument doc; 0161 QDomElement eff = doc.createElement(QStringLiteral("effect")); 0162 eff.setAttribute(QStringLiteral("tag"), id); 0163 eff.setAttribute(QStringLiteral("id"), id); 0164 0165 Mlt::Properties param_props(mlt_properties(metadata->get_data("parameters"))); 0166 for (int j = 0; param_props.is_valid() && j < param_props.count(); ++j) { 0167 QDomElement params = doc.createElement(QStringLiteral("parameter")); 0168 0169 Mlt::Properties paramdesc(mlt_properties(param_props.get_data(param_props.get_name(j)))); 0170 params.setAttribute(QStringLiteral("name"), paramdesc.get("identifier")); 0171 if (params.attribute(QStringLiteral("name")) == QLatin1String("argument")) { 0172 // This parameter has to be given as attribute when using command line, do not show it in Kdenlive 0173 continue; 0174 } 0175 0176 if (paramdesc.get("readonly") && (strcmp(paramdesc.get("readonly"), "yes") == 0)) { 0177 // Do not expose readonly parameters 0178 continue; 0179 } 0180 QString paramType = paramdesc.get("type"); 0181 0182 if (paramType == QLatin1String("float")) { 0183 // Float must be converted using correct locale 0184 if (paramdesc.get("maximum")) { 0185 params.setAttribute(QStringLiteral("max"), QString::number(paramdesc.get_double("maximum"), 'f')); 0186 } 0187 if (paramdesc.get("minimum")) { 0188 params.setAttribute(QStringLiteral("min"), QString::number(paramdesc.get_double("minimum"), 'f')); 0189 } 0190 } else { 0191 if (paramdesc.get("maximum")) { 0192 params.setAttribute(QStringLiteral("max"), paramdesc.get("maximum")); 0193 } 0194 if (paramdesc.get("minimum")) { 0195 params.setAttribute(QStringLiteral("min"), paramdesc.get("minimum")); 0196 } 0197 } 0198 0199 if (paramType == QLatin1String("integer")) { 0200 if (params.attribute(QStringLiteral("min")) == QLatin1String("0") && params.attribute(QStringLiteral("max")) == QLatin1String("1")) { 0201 params.setAttribute(QStringLiteral("type"), QStringLiteral("bool")); 0202 } else { 0203 params.setAttribute(QStringLiteral("type"), QStringLiteral("constant")); 0204 } 0205 } else if (paramType == QLatin1String("float")) { 0206 params.setAttribute(QStringLiteral("type"), QStringLiteral("constant")); 0207 // param type is float, set default decimals to 3 0208 params.setAttribute(QStringLiteral("decimals"), QStringLiteral("3")); 0209 } else if (paramType == QLatin1String("boolean")) { 0210 params.setAttribute(QStringLiteral("type"), QStringLiteral("bool")); 0211 } else if (paramType == QLatin1String("geometry")) { 0212 params.setAttribute(QStringLiteral("type"), QStringLiteral("geometry")); 0213 } else if (paramType == QLatin1String("string")) { 0214 // string parameter are not really supported, so if we have a default value, enforce it 0215 params.setAttribute(QStringLiteral("type"), QStringLiteral("fixed")); 0216 if (paramdesc.get("default")) { 0217 QString stringDefault = paramdesc.get("default"); 0218 stringDefault.remove(QLatin1Char('\'')); 0219 params.setAttribute(QStringLiteral("value"), stringDefault); 0220 } else { 0221 // String parameter without default, skip it completely 0222 continue; 0223 } 0224 } else { 0225 params.setAttribute(QStringLiteral("type"), paramType); 0226 if (!QString(paramdesc.get("format")).isEmpty()) { 0227 params.setAttribute(QStringLiteral("format"), paramdesc.get("format")); 0228 } 0229 } 0230 if (!params.hasAttribute(QStringLiteral("value"))) { 0231 if (paramType == QLatin1String("float")) { 0232 // floats have to be converted using correct locale 0233 if (paramdesc.get("default")) { 0234 params.setAttribute(QStringLiteral("default"), QString::number(paramdesc.get_double("default"), 'f')); 0235 } 0236 if (paramdesc.get("value")) { 0237 params.setAttribute(QStringLiteral("value"), QString::number(paramdesc.get_double("value"), 'f')); 0238 } else { 0239 params.setAttribute(QStringLiteral("value"), QString::number(paramdesc.get_double("default"), 'f')); 0240 } 0241 } else { 0242 if (paramdesc.get("default")) { 0243 params.setAttribute(QStringLiteral("default"), paramdesc.get("default")); 0244 } 0245 if (paramdesc.get("value")) { 0246 params.setAttribute(QStringLiteral("value"), paramdesc.get("value")); 0247 } else { 0248 params.setAttribute(QStringLiteral("value"), paramdesc.get("default")); 0249 } 0250 } 0251 } 0252 QString paramName = paramdesc.get("title"); 0253 if (paramName.isEmpty()) { 0254 paramName = paramdesc.get("identifier"); 0255 } 0256 if (!paramName.isEmpty()) { 0257 QDomElement pname = doc.createElement(QStringLiteral("name")); 0258 pname.appendChild(doc.createTextNode(paramName)); 0259 params.appendChild(pname); 0260 } 0261 if (paramdesc.get("description")) { 0262 QDomElement comment = doc.createElement(QStringLiteral("comment")); 0263 comment.appendChild(doc.createTextNode(paramdesc.get("description"))); 0264 params.appendChild(comment); 0265 } 0266 0267 eff.appendChild(params); 0268 } 0269 doc.appendChild(eff); 0270 res.xml = eff; 0271 return true; 0272 } else { 0273 res.id = res.mltId = assetId; 0274 qWarning() << "Empty metadata for " << assetId; 0275 return true; 0276 } 0277 } else { 0278 qWarning() << "Invalid metadata for " << assetId; 0279 } 0280 return false; 0281 } 0282 0283 template <typename AssetType> bool AbstractAssetsRepository<AssetType>::exists(const QString &assetId) const 0284 { 0285 return m_assets.count(assetId) > 0; 0286 } 0287 0288 template <typename AssetType> QVector<QPair<QString, QString>> AbstractAssetsRepository<AssetType>::getNames() const 0289 { 0290 QVector<QPair<QString, QString>> res; 0291 res.reserve(int(m_assets.size())); 0292 for (const auto &asset : m_assets) { 0293 if ((int(asset.second.type) == -1) || (!KdenliveSettings::gpu_accel() && asset.first.contains(QLatin1String("movit.")))) { 0294 // Hide GPU effects/compositions when movit disabled 0295 continue; 0296 } 0297 res.push_back({asset.first, asset.second.name}); 0298 } 0299 std::sort(res.begin(), res.end(), [](const QPair<QString, QString> &a, const QPair<QString, QString> &b) { return a.second < b.second; }); 0300 return res; 0301 } 0302 0303 template <typename AssetType> AssetType AbstractAssetsRepository<AssetType>::getType(const QString &assetId) const 0304 { 0305 Q_ASSERT(m_assets.count(assetId) > 0); 0306 return m_assets.at(assetId).type; 0307 } 0308 0309 template <typename AssetType> bool AbstractAssetsRepository<AssetType>::isUnique(const QString &assetId) const 0310 { 0311 if (m_assets.count(assetId) > 0) { 0312 return m_assets.at(assetId).xml.hasAttribute(QStringLiteral("unique")); 0313 } 0314 return false; 0315 } 0316 0317 template <typename AssetType> QString AbstractAssetsRepository<AssetType>::getName(const QString &assetId) const 0318 { 0319 Q_ASSERT(m_assets.count(assetId) > 0); 0320 return m_assets.at(assetId).name; 0321 } 0322 0323 template <typename AssetType> QString AbstractAssetsRepository<AssetType>::getDescription(const QString &assetId) const 0324 { 0325 Q_ASSERT(m_assets.count(assetId) > 0); 0326 return m_assets.at(assetId).description; 0327 } 0328 0329 template <typename AssetType> int AbstractAssetsRepository<AssetType>::getVersion(const QString &assetId) const 0330 { 0331 Q_ASSERT(m_assets.count(assetId) > 0); 0332 return m_assets.at(assetId).version; 0333 } 0334 0335 template <typename AssetType> bool AbstractAssetsRepository<AssetType>::parseInfoFromXml(const QDomElement ¤tAsset, Info &res) const 0336 { 0337 QString tag = currentAsset.attribute(QStringLiteral("tag"), QString()); 0338 QString id = currentAsset.attribute(QStringLiteral("id"), QString()); 0339 if (id.isEmpty()) { 0340 id = tag; 0341 } 0342 0343 if (!exists(tag)) { 0344 qDebug() << "plugin not available:" << tag; 0345 return false; 0346 } 0347 0348 // Check if there is a maximal version set 0349 if (currentAsset.hasAttribute(QStringLiteral("version")) && !m_assets.at(tag).xml.isNull()) { 0350 // a specific version of the filter is required 0351 if (m_assets.at(tag).version < int(100 * currentAsset.attribute(QStringLiteral("version")).toDouble())) { 0352 qDebug() << "plugin version too low:" << tag; 0353 return false; 0354 } 0355 } 0356 0357 res = m_assets.at(tag); 0358 res.id = id; 0359 res.mltId = tag; 0360 0361 // Update description if the xml provide one 0362 QString description = Xml::getSubTagContent(currentAsset, QStringLiteral("description")); 0363 if (!description.isEmpty()) { 0364 res.description = i18n(description.toUtf8().constData()) + QString(" (%1)").arg(res.mltId); 0365 } 0366 0367 // Update name if the xml provide one 0368 QString name = Xml::getSubTagContent(currentAsset, QStringLiteral("name")); 0369 if (!name.isEmpty()) { 0370 res.name = i18n(name.toUtf8().constData()); 0371 } 0372 return true; 0373 } 0374 0375 template <typename AssetType> QDomElement AbstractAssetsRepository<AssetType>::getXml(const QString &assetId) const 0376 { 0377 if (m_assets.count(assetId) == 0) { 0378 qWarning() << "Unknown transition" << assetId; 0379 return QDomElement(); 0380 } 0381 return m_assets.at(assetId).xml.cloneNode().toElement(); 0382 }