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 &currentAsset, 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 }