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