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 }