abstractassetsrepository.ipp 13.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/***************************************************************************
 *   Copyright (C) 2017 by Nicolas Carion                                  *
 *   This file is part of Kdenlive. See www.kdenlive.org.                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) version 3 or any later version accepted by the       *
 *   membership of KDE e.V. (or its successor approved  by the membership  *
 *   of KDE e.V.), which shall act as a proxy defined in Section 14 of     *
 *   version 3 of the license.                                             *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

#include "xml/xml.hpp"
#include <QDir>
Nicolas Carion's avatar
Nicolas Carion committed
24
#include <QFile>
25
#include <QStandardPaths>
Nicolas Carion's avatar
Nicolas Carion committed
26
#include <QString>
27
28
29
30
31
32
33
#include <QTextStream>

#include <locale>
#ifdef Q_OS_MAC
#include <xlocale.h>
#endif

Nicolas Carion's avatar
Nicolas Carion committed
34
template <typename AssetType> AbstractAssetsRepository<AssetType>::AbstractAssetsRepository() = default;
35

Nicolas Carion's avatar
Nicolas Carion committed
36
template <typename AssetType> void AbstractAssetsRepository<AssetType>::init()
37
{
Nicolas Carion's avatar
Nicolas Carion committed
38
// Warning: Mlt::Factory::init() resets the locale to the default system value, make sure we keep correct locale
39
40
41
42
43
44
#ifndef Q_OS_MAC
    setlocale(LC_NUMERIC, nullptr);
#else
    setlocale(LC_NUMERIC_MASK, nullptr);
#endif

45
    // Parse blacklist
46
47
    parseAssetList(assetBlackListPath(), m_blacklist);

48
    // Parse preferred list
49
    parseAssetList(assetPreferredListPath(), m_preferred_list);
50
51
52
53

    // Retrieve the list of MLT's available assets.
    QScopedPointer<Mlt::Properties> assets(retrieveListFromMlt());
    int max = assets->count();
54
    QString sox = QStringLiteral("sox.");
55
56
57
    for (int i = 0; i < max; ++i) {
        Info info;
        QString name = assets->get_name(i);
Nicolas Carion's avatar
Nicolas Carion committed
58
        info.id = name;
59
60
61
62
        if (name.startsWith(sox)) {
            // sox effects are not usage directly (parameters not available)
            continue;
        }
63
        // qDebug() << "trying to parse " <<name <<" blacklist="<<m_blacklist.contains(name);
64
65
66
67
68
69
        if (!m_blacklist.contains(name) && parseInfoFromMlt(name, info)) {
            m_assets[name] = info;
        } else {
            if (m_blacklist.contains(name)) {
                qDebug() << name << "is blacklisted";
            } else {
Nicolas Carion's avatar
Nicolas Carion committed
70
                qDebug() << "WARNING : Fails to parse " << name;
71
72
73
74
75
            }
        }
    }

    // We now parse custom effect xml
76

77
78
79
    // Set the directories to look into for effects.
    QStringList asset_dirs = assetDirs();

80
    /* Parsing of custom xml works as follows: we parse all custom files.
Nicolas Carion's avatar
Nicolas Carion committed
81
82
83
       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
       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
       list, while discarding the bare version of each tag (the one with no file associated)
84
85
    */
    std::unordered_map<QString, Info> customAssets;
86
87
88
    // reverse order to prioritize local install
    QListIterator<QString> dirs_it(asset_dirs);
    for (dirs_it.toBack(); dirs_it.hasPrevious();) { auto dir=dirs_it.previous();
89
90
91
92
        QDir current_dir(dir);
        QStringList filter;
        filter << QStringLiteral("*.xml");
        QStringList fileList = current_dir.entryList(filter, QDir::Files);
Nicolas Carion's avatar
Nicolas Carion committed
93
        for (const auto &file : fileList) {
94
            QString path = current_dir.absoluteFilePath(file);
95
96
97
98
            parseCustomAssetFile(path, customAssets);
        }
    }

Nicolas Carion's avatar
Nicolas Carion committed
99
100
    // We add the custom assets
    for (const auto &custom : customAssets) {
101
102
103
        // Custom assets should override default ones
        m_assets[custom.first] = custom.second;
        /*if (m_assets.count(custom.second.mltId) > 0) {
104
105
106
107
108
            m_assets.erase(custom.second.mltId);
        }
        if (m_assets.count(custom.first) == 0) {
            m_assets[custom.first] = custom.second;
        } else {
Nicolas Carion's avatar
Nicolas Carion committed
109
            qDebug() << "Error: conflicting asset name " << custom.first;
110
        }*/
111
112
113
    }
}

114
template <typename AssetType> void AbstractAssetsRepository<AssetType>::parseAssetList(const QString &filePath, QSet<QString> &destination)
115
{
116
117
    if (filePath.isEmpty())
        return;
118
119
120
    QFile assetFile(filePath);
    if (assetFile.open(QIODevice::ReadOnly)) {
        QTextStream stream(&assetFile);
121
122
123
124
        QString line;
        while (stream.readLineInto(&line)) {
            line = line.simplified();
            if (!line.isEmpty() && !line.startsWith('#')) {
125
                destination.insert(line);
126
127
128
129
130
            }
        }
    }
}

Nicolas Carion's avatar
Nicolas Carion committed
131
template <typename AssetType> bool AbstractAssetsRepository<AssetType>::parseInfoFromMlt(const QString &assetId, Info &res)
Nicolas Carion's avatar
Nicolas Carion committed
132
133
134
135
{
    QScopedPointer<Mlt::Properties> metadata(getMetadata(assetId));
    if (metadata && metadata->is_valid()) {
        if (metadata->get("title") && metadata->get("identifier") && strlen(metadata->get("title")) > 0) {
136
            QString id = metadata->get("identifier");
Nicolas Carion's avatar
Nicolas Carion committed
137
138
139
            res.name = metadata->get("title");
            res.name[0] = res.name[0].toUpper();
            res.description = metadata->get("description");
140
            res.description.append(QString(" (%1)").arg(id));
Nicolas Carion's avatar
Nicolas Carion committed
141
142
            res.author = metadata->get("creator");
            res.version_str = metadata->get("version");
143
            res.version = ceil(100 * metadata->get_double("version"));
144
            res.id = res.mltId = assetId;
Nicolas Carion's avatar
Nicolas Carion committed
145
            parseType(metadata, res);
146
147
148
149
150
151
152
            // Create params
            QDomDocument doc;
            QDomElement eff = doc.createElement(QStringLiteral("effect"));
            eff.setAttribute(QStringLiteral("tag"), id);
            eff.setAttribute(QStringLiteral("id"), id);
            ////qCDebug(KDENLIVE_LOG)<<"Effect: "<<id;

Nicolas Carion's avatar
Nicolas Carion committed
153
            Mlt::Properties param_props((mlt_properties)metadata->get_data("parameters"));
154
155
156
            for (int j = 0; param_props.is_valid() && j < param_props.count(); ++j) {
                QDomElement params = doc.createElement(QStringLiteral("parameter"));

Nicolas Carion's avatar
Nicolas Carion committed
157
                Mlt::Properties paramdesc((mlt_properties)param_props.get_data(param_props.get_name(j)));
158
159
160
161
162
163
                params.setAttribute(QStringLiteral("name"), paramdesc.get("identifier"));
                if (params.attribute(QStringLiteral("name")) == QLatin1String("argument")) {
                    // This parameter has to be given as attribute when using command line, do not show it in Kdenlive
                    continue;
                }

Nicolas Carion's avatar
Nicolas Carion committed
164
                if (paramdesc.get("readonly") && (strcmp(paramdesc.get("readonly"), "yes") == 0)) {
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
                    // Do not expose readonly parameters
                    continue;
                }

                if (paramdesc.get("maximum")) {
                    params.setAttribute(QStringLiteral("max"), paramdesc.get("maximum"));
                }
                if (paramdesc.get("minimum")) {
                    params.setAttribute(QStringLiteral("min"), paramdesc.get("minimum"));
                }

                QString paramType = paramdesc.get("type");
                if (paramType == QLatin1String("integer")) {
                    if (params.attribute(QStringLiteral("min")) == QLatin1String("0") && params.attribute(QStringLiteral("max")) == QLatin1String("1")) {
                        params.setAttribute(QStringLiteral("type"), QStringLiteral("bool"));
                    } else {
                        params.setAttribute(QStringLiteral("type"), QStringLiteral("constant"));
                    }
                } else if (paramType == QLatin1String("float")) {
                    params.setAttribute(QStringLiteral("type"), QStringLiteral("constant"));
                    // param type is float, set default decimals to 3
                    params.setAttribute(QStringLiteral("decimals"), QStringLiteral("3"));
                } else if (paramType == QLatin1String("boolean")) {
                    params.setAttribute(QStringLiteral("type"), QStringLiteral("bool"));
                } else if (paramType == QLatin1String("geometry")) {
                    params.setAttribute(QStringLiteral("type"), QStringLiteral("geometry"));
                } else if (paramType == QLatin1String("string")) {
                    // string parameter are not really supported, so if we have a default value, enforce it
                    params.setAttribute(QStringLiteral("type"), QStringLiteral("fixed"));
                    if (paramdesc.get("default")) {
                        QString stringDefault = paramdesc.get("default");
                        stringDefault.remove(QLatin1Char('\''));
                        params.setAttribute(QStringLiteral("value"), stringDefault);
                    } else {
                        // String parameter without default, skip it completely
                        continue;
                    }
                } else {
                    params.setAttribute(QStringLiteral("type"), paramType);
                    if (!QString(paramdesc.get("format")).isEmpty()) {
                        params.setAttribute(QStringLiteral("format"), paramdesc.get("format"));
                    }
                }
                if (!params.hasAttribute(QStringLiteral("value"))) {
                    if (paramdesc.get("default")) {
                        params.setAttribute(QStringLiteral("default"), paramdesc.get("default"));
                    }
                    if (paramdesc.get("value")) {
                        params.setAttribute(QStringLiteral("value"), paramdesc.get("value"));
                    } else {
                        params.setAttribute(QStringLiteral("value"), paramdesc.get("default"));
                    }
                }
                QString paramName = paramdesc.get("title");
                if (!paramName.isEmpty()) {
                    QDomElement pname = doc.createElement(QStringLiteral("name"));
                    pname.appendChild(doc.createTextNode(paramName));
                    params.appendChild(pname);
                }
                if (paramdesc.get("description")) {
                    QDomElement comment = doc.createElement(QStringLiteral("comment"));
                    comment.appendChild(doc.createTextNode(paramdesc.get("description")));
                    params.appendChild(comment);
                }

                eff.appendChild(params);
            }
            doc.appendChild(eff);
            res.xml = eff;
Nicolas Carion's avatar
Nicolas Carion committed
234
235
236
237
238
            return true;
        }
    }
    return false;
}
239

Nicolas Carion's avatar
Nicolas Carion committed
240
template <typename AssetType> bool AbstractAssetsRepository<AssetType>::exists(const QString &assetId) const
241
242
243
244
{
    return m_assets.count(assetId) > 0;
}

Nicolas Carion's avatar
Nicolas Carion committed
245
template <typename AssetType> QVector<QPair<QString, QString>> AbstractAssetsRepository<AssetType>::getNames() const
246
{
Nicolas Carion's avatar
Nicolas Carion committed
247
    QVector<QPair<QString, QString>> res;
248
    res.reserve((int)m_assets.size());
Nicolas Carion's avatar
Nicolas Carion committed
249
    for (const auto &asset : m_assets) {
250
251
        res.push_back({asset.first, asset.second.name});
    }
252
    std::sort(res.begin(), res.end(), [](const QPair<QString, QString> &a, const QPair<QString, QString> &b) { return a.second < b.second; });
253
254
255
    return res;
}

Nicolas Carion's avatar
Nicolas Carion committed
256
template <typename AssetType> AssetType AbstractAssetsRepository<AssetType>::getType(const QString &assetId) const
257
258
259
260
261
{
    Q_ASSERT(m_assets.count(assetId) > 0);
    return m_assets.at(assetId).type;
}

Nicolas Carion's avatar
Nicolas Carion committed
262
template <typename AssetType> QString AbstractAssetsRepository<AssetType>::getName(const QString &assetId) const
263
264
265
266
267
{
    Q_ASSERT(m_assets.count(assetId) > 0);
    return m_assets.at(assetId).name;
}

Nicolas Carion's avatar
Nicolas Carion committed
268
template <typename AssetType> QString AbstractAssetsRepository<AssetType>::getDescription(const QString &assetId) const
269
270
271
272
273
{
    Q_ASSERT(m_assets.count(assetId) > 0);
    return m_assets.at(assetId).description;
}

Nicolas Carion's avatar
Nicolas Carion committed
274
template <typename AssetType> bool AbstractAssetsRepository<AssetType>::parseInfoFromXml(const QDomElement &currentAsset, Info &res) const
Nicolas Carion's avatar
Nicolas Carion committed
275
{
276
    QString tag = currentAsset.attribute(QStringLiteral("tag"), QString());
Nicolas Carion's avatar
Nicolas Carion committed
277

278
279
280
281
282
283
284
285
    QString id = currentAsset.attribute(QStringLiteral("id"), QString());
    if (id.isEmpty()) {
        id = tag;
    }

    if (!exists(tag)) {
        qDebug() << "++++++ Unknown asset : " << tag;
        return false;
Nicolas Carion's avatar
Nicolas Carion committed
286
287
    }

Nicolas Carion's avatar
Nicolas Carion committed
288
    // Check if there is a maximal version set
Nicolas Carion's avatar
Nicolas Carion committed
289
290
    if (currentAsset.hasAttribute(QStringLiteral("version"))) {
        // a specific version of the filter is required
291
        if (m_assets.at(tag).version < (int)(100 * currentAsset.attribute(QStringLiteral("version")).toDouble())) {
292
            return false;
Nicolas Carion's avatar
Nicolas Carion committed
293
294
295
        }
    }

296
297
298
299
    res = m_assets.at(tag);
    res.id = id;
    res.mltId = tag;

Nicolas Carion's avatar
Nicolas Carion committed
300
    // Update description if the xml provide one
Nicolas Carion's avatar
Nicolas Carion committed
301
302
    QString description = Xml::getSubTagContent(currentAsset, QStringLiteral("description"));
    if (!description.isEmpty()) {
303
        res.description = description;
Nicolas Carion's avatar
Nicolas Carion committed
304
305
    }

Nicolas Carion's avatar
Nicolas Carion committed
306
    // Update name if the xml provide one
Nicolas Carion's avatar
Nicolas Carion committed
307
308
    QString name = Xml::getSubTagContent(currentAsset, QStringLiteral("name"));
    if (!name.isEmpty()) {
309
        res.name = name;
Nicolas Carion's avatar
Nicolas Carion committed
310
    }
311
    return true;
Nicolas Carion's avatar
Nicolas Carion committed
312
}
313

Nicolas Carion's avatar
Nicolas Carion committed
314
template <typename AssetType> QDomElement AbstractAssetsRepository<AssetType>::getXml(const QString &assetId) const
315
{
316
    if (m_assets.count(assetId) == 0) {
Nicolas Carion's avatar
Nicolas Carion committed
317
        qDebug() << "Error : Requesting info on unknown transition " << assetId;
318
319
        return QDomElement();
    }
320
    return m_assets.at(assetId).xml.cloneNode().toElement();
321
}