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