assetparametermodel.cpp 53.9 KB
Newer Older
1
/*
2
    SPDX-FileCopyrightText: 2017 Nicolas Carion
Camille Moulin's avatar
Camille Moulin committed
3
    SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
4
*/
5
6

#include "assetparametermodel.hpp"
7
#include "assets/keyframes/model/keyframemodellist.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
8
#include "core.h"
9
#include "kdenlivesettings.h"
Nicolas Carion's avatar
linting    
Nicolas Carion committed
10
#include "klocalizedstring.h"
Nicolas Carion's avatar
Nicolas Carion committed
11
#include "profiles/profilemodel.hpp"
12
#include <QDebug>
13
#include <QDir>
14
15
#include <QJsonArray>
#include <QJsonObject>
Nicolas Carion's avatar
Nicolas Carion committed
16
#include <QString>
17
18
#include <QDir>
#include <QDirIterator>
19
#include <QRegularExpression>
Simon Eugster's avatar
Simon Eugster committed
20
#include <effects/effectsrepository.hpp>
21
#define DEBUG_LOCALE false
22

23
24
static QVector<int> bypassRoles = {AssetParameterModel::InRole,AssetParameterModel::OutRole,AssetParameterModel::ParentInRole,AssetParameterModel::ParentDurationRole,AssetParameterModel::ParentPositionRole,AssetParameterModel::HideKeyframesFirstRole};

25
AssetParameterModel::AssetParameterModel(std::unique_ptr<Mlt::Properties> asset, const QDomElement &assetXml, const QString &assetId, ObjectId ownerId,
26
                                         const QString& originalDecimalPoint, QObject *parent)
27
    : QAbstractListModel(parent)
28
    , monitorId(ownerId.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor)
29
    , m_assetId(assetId)
30
    , m_ownerId(ownerId)
31
    , m_active(false)
32
    , m_asset(std::move(asset))
33
    , m_keyframes(nullptr)
34
    , m_activeKeyframe(-1)
35
    , m_filterProgress(0)
36
{
37
    Q_ASSERT(m_asset->is_valid());
38
    QDomNodeList parameterNodes = assetXml.elementsByTagName(QStringLiteral("parameter"));
39
    m_hideKeyframesByDefault = assetXml.hasAttribute(QStringLiteral("hideKeyframes"));
40
    m_isAudio = assetXml.attribute(QStringLiteral("type")) == QLatin1String("audio");
41
42

    bool needsLocaleConversion = false;
Nicolas Carion's avatar
Nicolas Carion committed
43
    QChar separator, oldSeparator;
44
    // Check locale, default effects xml has no LC_NUMERIC defined and always uses the C locale
45
    if (assetXml.hasAttribute(QStringLiteral("LC_NUMERIC"))) {
46
        QLocale effectLocale = QLocale(assetXml.attribute(QStringLiteral("LC_NUMERIC"))); // Check if effect has a special locale → probably OK
47
        if (QLocale::c().decimalPoint() != effectLocale.decimalPoint()) {
48
            needsLocaleConversion = true;
49
50
            separator = QLocale::c().decimalPoint();
            oldSeparator = effectLocale.decimalPoint();
51
52
53
        }
    }

54
55
#if false
    // Debut test  stuff. Warning, assets can also come from TransitionsRepository depending on owner type
Simon Eugster's avatar
Simon Eugster committed
56
57
58
59
60
61
62
63
64
    if (EffectsRepository::get()->exists(assetId)) {
        qDebug() << "Asset " << assetId << " found in the repository. Description: " << EffectsRepository::get()->getDescription(assetId);
        QString str;
        QTextStream stream(&str);
        EffectsRepository::get()->getXml(assetId).save(stream, 4);
        qDebug() << "Asset XML: " << str;
    } else {
        qDebug() << "Asset not found in repo: " << assetId;
    }
65
#endif
Simon Eugster's avatar
Simon Eugster committed
66

67
68
69
70
71
72
73
74
    qDebug() << "XML parsing of " << assetId << ". found" << parameterNodes.count() << "parameters";

    if (DEBUG_LOCALE) {
        QString str;
        QTextStream stream(&str);
        assetXml.save(stream, 1);
        qDebug() << "XML to parse: " << str;
    }
75
76
    bool fixDecimalPoint = !originalDecimalPoint.isEmpty();
    if (fixDecimalPoint) {
77
78
79
80
        qDebug() << "Original decimal point was different:" << originalDecimalPoint << "Values will be converted if required.";
    }
    for (int i = 0; i < parameterNodes.count(); ++i) {
        QDomElement currentParameter = parameterNodes.item(i).toElement();
81

Nicolas Carion's avatar
Nicolas Carion committed
82
        // Convert parameters if we need to
83
84
        // Note: This is not directly related to the originalDecimalPoint parameter.
        // Is it still required? Does it work correctly for non-number values (e.g. lists which contain commas)?
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
        if (needsLocaleConversion) {
            QDomNamedNodeMap attrs = currentParameter.attributes();
            for (int k = 0; k < attrs.count(); ++k) {
                QString nodeName = attrs.item(k).nodeName();
                if (nodeName != QLatin1String("type") && nodeName != QLatin1String("name")) {
                    QString val = attrs.item(k).nodeValue();
                    if (val.contains(oldSeparator)) {
                        QString newVal = val.replace(oldSeparator, separator);
                        attrs.item(k).setNodeValue(newVal);
                    }
                }
            }
        }
        // Parse the basic attributes of the parameter
        QString name = currentParameter.attribute(QStringLiteral("name"));
        QString type = currentParameter.attribute(QStringLiteral("type"));
        QString value = currentParameter.attribute(QStringLiteral("value"));
102
103
104
        ParamRow currentRow;
        currentRow.type = paramTypeFromStr(type);
        currentRow.xml = currentParameter;
105
        if (value.isEmpty()) {
106
            QVariant defaultValue = parseAttribute(m_ownerId, QStringLiteral("default"), currentParameter);
Simon Eugster's avatar
Simon Eugster committed
107
108
            value = defaultValue.toString();
            qDebug() << "QLocale: Default value is" << defaultValue << "parsed:" << value;
109
        }
110
        bool isFixed = (type == QLatin1String("fixed"));
111
112
        if (isFixed) {
            m_fixedParams[name] = value;
113
        } else if (currentRow.type == ParamType::Position) {
114
115
116
            int val = value.toInt();
            if (val < 0) {
                int in = pCore->getItemIn(m_ownerId);
117
                int out = in + pCore->getItemDuration(m_ownerId) - 1;
118
119
120
                val += out;
                value = QString::number(val);
            }
121
        } else if (currentRow.type == ParamType::KeyframeParam || currentRow.type == ParamType::AnimatedRect || currentRow.type == ParamType::ColorWheel) {
122
            if (!value.contains(QLatin1Char('='))) {
123
                value.prepend(QStringLiteral("%1=").arg(pCore->getItemIn(m_ownerId)));
124
            }
125
126
127
128
        }

        if (fixDecimalPoint) {
            bool converted = true;
Simon Eugster's avatar
Simon Eugster committed
129
            QString originalValue(value);
130
131
132
            switch (currentRow.type) {
                case ParamType::KeyframeParam:
                case ParamType::Position:
133
                    // Fix values like <position>=1,5
134
                    value.replace(QRegularExpression(R"((=\d+),(\d+))"), "\\1.\\2");
135
136
                    break;
                case ParamType::AnimatedRect:
137
                    // Fix values like <position>=50 20 1920 1080 0,75
138
                    value.replace(QRegularExpression(R"((=\d+ \d+ \d+ \d+ \d+),(\d+))"), "\\1.\\2");
139
                    break;
Simon Eugster's avatar
Simon Eugster committed
140
141
142
143
                case ParamType::ColorWheel:
                    // Colour wheel has 3 separate properties: prop_r, prop_g and prop_b, always numbers
                case ParamType::Double:
                case ParamType::Hidden:
144
                case ParamType::List:
145
                case ParamType::ListWithDependency:
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
                    // Despite its name, a list type parameter is a single value *chosen from* a list.
                    // If it contains a non-“.” decimal separator, it is very likely wrong.
                    // Fall-through, treat like Double
                case ParamType::Bezier_spline:
                    value.replace(originalDecimalPoint, ".");
                    break;
                case ParamType::Bool:
                case ParamType::Color:
                case ParamType::Fontfamily:
                case ParamType::Keywords:
                case ParamType::Readonly:
                case ParamType::RestrictedAnim: // Fine because unsupported
                case ParamType::Animated: // Fine because unsupported
                case ParamType::Addedgeometry: // Fine because unsupported
                case ParamType::Url:
161
                case ParamType::UrlList:
162
163
164
165
166
167
                    // All fine
                    converted = false;
                    break;
                case ParamType::Curve:
                case ParamType::Geometry:
                case ParamType::Switch:
168
                case ParamType::MultiSwitch:
169
170
171
172
173
174
175
176
177
                case ParamType::Wipe:
                    // Pretty sure that those are fine
                    converted = false;
                    break;
                case ParamType::Roto_spline: // Not sure because cannot test
                case ParamType::Filterjob:
                    // Not sure if fine
                    converted = false;
                    break;
178
            }
179
            if (converted) {
Simon Eugster's avatar
Simon Eugster committed
180
181
182
183
184
                if (value != originalValue) {
                    qDebug() << "Decimal point conversion: " << name << "converted from" << originalValue << "to" << value;
                } else {
                    qDebug() << "Decimal point conversion: " << name << " is already ok: " << value;
                }
185
186
            } else {
                qDebug() << "No fixing needed for" << name << "=" << value;
187
            }
188
        }
189
        if (currentRow.type == ParamType::UrlList && !m_asset->property_exists(name.toUtf8().constData())) {
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
            QString values = currentParameter.attribute(QStringLiteral("paramlist"));
            if (values == QLatin1String("%lutPaths")) {
                QString filter = currentParameter.attribute(QStringLiteral("filter"));;
                filter.remove(0, filter.indexOf("(")+1);
                filter.remove(filter.indexOf(")")-1, -1);
                QStringList fileExt = filter.split(" ");
                // check for Kdenlive installed luts files
                QStringList customLuts = QStandardPaths::locateAll(QStandardPaths::AppLocalDataLocation, QStringLiteral("luts"), QStandardPaths::LocateDirectory);
                QStringList results;
                for (const QString &folderpath : qAsConst(customLuts)) {
                    QDir dir(folderpath);
                    QDirIterator it(dir.absolutePath(), fileExt, QDir::Files, QDirIterator::Subdirectories);
                    while (it.hasNext()) {
                        results.append(it.next());
                        break;
                    }
                }
                if (!results.isEmpty()) {
                    value = results.first();
                }
            }
        }

213
214
215
216
217
218
        if (!isFixed) {
            currentRow.value = value;
            QString title = i18n(currentParameter.firstChildElement(QStringLiteral("name")).text().toUtf8().data());
            currentRow.name = title.isEmpty() ? name : title;
            m_params[name] = currentRow;
        }
219
        if (!name.isEmpty()) {
220
            internalSetParameter(name, value);
221
222
223
            // Keep track of param order
            m_paramOrder.push_back(name);
        }
224

225
        if (isFixed) {
Nicolas Carion's avatar
Nicolas Carion committed
226
            // fixed parameters are not displayed so we don't store them.
227
228
229
230
            continue;
        }
        m_rows.push_back(name);
    }
231
232
233
234
235
236
237
238
    if (m_assetId.startsWith(QStringLiteral("sox_"))) {
        // Sox effects need to have a special "Effect" value set
        QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
        for (const QString &pName : m_paramOrder) {
            effectParam << m_asset->get(pName.toUtf8().constData());
        }
        m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
    }
239

Nicolas Carion's avatar
Nicolas Carion committed
240
    qDebug() << "END parsing of " << assetId << ". Number of found parameters" << m_rows.size();
Vincent Pinon's avatar
Vincent Pinon committed
241
    modelChanged();
242
243
}

244
245
void AssetParameterModel::prepareKeyframes()
{
Nicolas Carion's avatar
Nicolas Carion committed
246
    if (m_keyframes) return;
247
    int ix = 0;
Vincent Pinon's avatar
Vincent Pinon committed
248
    for (const auto &name : qAsConst(m_rows)) {
249
        if (m_params.at(name).type == ParamType::KeyframeParam || m_params.at(name).type == ParamType::AnimatedRect ||
250
            m_params.at(name).type == ParamType::Roto_spline || m_params.at(name).type == ParamType::ColorWheel) {
Nicolas Carion's avatar
Nicolas Carion committed
251
            addKeyframeParam(index(ix, 0));
252
        }
253
        ix++;
254
    }
255
256
257
258
    if (m_keyframes) {
        // Make sure we have keyframes at same position for all parameters
        m_keyframes->checkConsistency();
    }
259
260
}

261
262
263
264
265
QStringList AssetParameterModel::getKeyframableParameters() const
{
    QStringList paramNames;
    int ix = 0;
    for (const auto &name : m_rows) {
266
        if (m_params.at(name).type == ParamType::KeyframeParam || m_params.at(name).type == ParamType::AnimatedRect || m_params.at(name).type == ParamType::ColorWheel) {
267
268
269
270
271
272
273
274
            //addKeyframeParam(index(ix, 0));
            paramNames << name;
        }
        ix++;
    }
    return paramNames;
}

275
276
277
278
279
280
const QString AssetParameterModel::getParam(const QString &paramName)
{
    Q_ASSERT(m_asset->is_valid());
    return m_asset->get(paramName.toUtf8().constData());
}

Nicolas Carion's avatar
Nicolas Carion committed
281
void AssetParameterModel::setParameter(const QString &name, int value, bool update)
282
283
284
285
286
287
288
289
{
    Q_ASSERT(m_asset->is_valid());
    m_asset->set(name.toLatin1().constData(), value);
    if (m_fixedParams.count(name) == 0) {
        m_params[name].value = value;
    } else {
        m_fixedParams[name] = value;
    }
290
291
292
293
294
295
    if (m_assetId.startsWith(QStringLiteral("sox_"))) {
        // Warning, SOX effect, need unplug/replug
        qDebug() << "// Warning, SOX effect, need unplug/replug";
        QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
        for (const QString &pName : m_paramOrder) {
            effectParam << m_asset->get(pName.toUtf8().constData());
296
        }
297
298
        m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
        emit replugEffect(shared_from_this());
299
    } else if (m_assetId.startsWith(QStringLiteral("ladspa"))) {
300
301
302
303
304
305
        // these effects don't understand param change and need to be rebuild
        emit replugEffect(shared_from_this());
    }
    if (update) {
        emit modelChanged();
        emit dataChanged(index(0, 0), index(m_rows.count() - 1, 0), {});
306
        // Update fades in timeline
307
        pCore->updateItemModel(m_ownerId, m_assetId);
308
309
310
311
312
313
        if (!m_isAudio) {
            // Trigger monitor refresh
            pCore->refreshProjectItem(m_ownerId);
            // Invalidate timeline preview
            pCore->invalidateItem(m_ownerId);
        }
314
315
316
    }
}

317
void AssetParameterModel::internalSetParameter(const QString &name, const QString &paramValue, const QModelIndex &paramIndex)
318
{
319
    Q_ASSERT(m_asset->is_valid());
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
320
    // TODO: this does not really belong here, but I don't see another way to do it so that undo works
321
322
323
    if (m_params.count(name) > 0) {
        ParamType type = m_params.at(name).type;
        if (type == ParamType::Curve) {
Laurent Montel's avatar
Laurent Montel committed
324
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
325
            QStringList vals = paramValue.split(QLatin1Char(';'), QString::SkipEmptyParts);
Laurent Montel's avatar
Laurent Montel committed
326
#else
327
            QStringList vals = paramValue.split(QLatin1Char(';'), Qt::SkipEmptyParts);
Laurent Montel's avatar
Laurent Montel committed
328
#endif
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
            int points = vals.size();
            m_asset->set("3", points / 10.);
            m_params[QStringLiteral("3")].value = points / 10.;
            // for the curve, inpoints are numbered: 6, 8, 10, 12, 14
            // outpoints, 7, 9, 11, 13,15 so we need to deduce these enums
            for (int i = 0; i < points; i++) {
                const QString &pointVal = vals.at(i);
                int idx = 2 * i + 6;
                QString pName = QString::number(idx);
                double val = pointVal.section(QLatin1Char('/'), 0, 0).toDouble();
                m_asset->set(pName.toLatin1().constData(), val);
                m_params[pName].value = val;
                idx++;
                pName = QString::number(idx);
                val = pointVal.section(QLatin1Char('/'), 1, 1).toDouble();
                m_asset->set(pName.toLatin1().constData(), val);
                m_params[pName].value = val;
            }
        } else if (type == ParamType::MultiSwitch) {
            QStringList names = name.split(QLatin1Char('\n'));
            QStringList values = paramValue.split(QLatin1Char('\n'));
            if (names.count() == values.count()) {
                for (int i = 0; i < names.count(); i++) {
                    m_asset->set(names.at(i).toLatin1().constData(), values.at(i).toLatin1().constData());
                }
                m_params[name].value = paramValue;
            }
            return;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
357
358
        }
    }
359
    bool conversionSuccess = true;
Simon Eugster's avatar
Simon Eugster committed
360
    double doubleValue = paramValue.toDouble(&conversionSuccess);
361
362
    if (conversionSuccess) {
        m_asset->set(name.toLatin1().constData(), doubleValue);
363
        if (m_fixedParams.count(name) == 0) {
364
            m_params[name].value = doubleValue;
365
366
        } else {
            m_fixedParams[name] = doubleValue;
367
        }
368
    } else {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
369
        m_asset->set(name.toLatin1().constData(), paramValue.toUtf8().constData());
370
        if (m_fixedParams.count(name) == 0) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
371
            m_params[name].value = paramValue;
372
            if (m_keyframes) {
373
374
                // This is a fake query to force the animation to be parsed
                (void)m_asset->anim_get_int(name.toLatin1().constData(), 0, -1);
375
376
377
                KeyframeModel *km = m_keyframes->getKeyModel(paramIndex);
                if (km) {
                    km->refresh();
378
                } else {
379
                    qDebug()<<"====ERROR KFMODEL NOT FOUND FOR: "<<name<<", "<<paramIndex;
380
381
                }
                //m_keyframes->refresh();
382
383
            }
        } else {
384
            m_fixedParams[name] = paramValue;
385
386
        }
    }
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
    // Fades need to have their alpha or level param synced to in/out
    if (m_assetId.startsWith(QLatin1String("fade_")) && (name == QLatin1String("in") || name == QLatin1String("out"))) {
        if (m_assetId.startsWith(QLatin1String("fade_from"))) {
            if (getAsset()->get("alpha") == QLatin1String("1")) {
                // Adjust level value to match filter end
                getAsset()->set("level", "0=0;-1=1");
            } else if (getAsset()->get("level") == QLatin1String("1")) {
                getAsset()->set("alpha", "0=0;-1=1");
            }
        } else {
            if (getAsset()->get("alpha") == QLatin1String("1")) {
                // Adjust level value to match filter end
                getAsset()->set("level", "0=1;-1=0");
            } else if (getAsset()->get("level") == QLatin1String("1")) {
                getAsset()->set("alpha", "0=1;-1=0");
            }
        }
    }
405
    qDebug() << " = = SET EFFECT PARAM: " << name << " = " << m_asset->get(name.toLatin1().constData());
406
407
}

408
void AssetParameterModel::setParameter(const QString &name, const QString &paramValue, bool update, const QModelIndex &paramIndex)
409
{
410
    // qDebug() << "// PROCESSING PARAM CHANGE: " << name << ", UPDATE: " << update << ", VAL: " << paramValue;
411
412
    internalSetParameter(name, paramValue, paramIndex);
    bool updateChildRequired = true;
413
414
415
416
417
418
419
420
    if (m_assetId.startsWith(QStringLiteral("sox_"))) {
        // Warning, SOX effect, need unplug/replug
        QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
        for (const QString &pName : m_paramOrder) {
            effectParam << m_asset->get(pName.toUtf8().constData());
        }
        m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
        emit replugEffect(shared_from_this());
421
        updateChildRequired = false;
422
    } else if (m_assetId.startsWith(QStringLiteral("ladspa"))) {
Nicolas Carion's avatar
Nicolas Carion committed
423
424
        // these effects don't understand param change and need to be rebuild
        emit replugEffect(shared_from_this());
425
426
427
428
429
430
431
432
433
        updateChildRequired = false;
    } else if (update) {
        qDebug() << "// SENDING DATA CHANGE....";
        if (paramIndex.isValid()) {
            emit dataChanged(paramIndex, paramIndex);
        } else {
            QModelIndex ix = index(m_rows.indexOf(name), 0);
            emit dataChanged(ix, ix);
        }
434
435
        emit modelChanged();
    }
436
    if (updateChildRequired) {
437
        emit updateChildren({name});
438
439
440
441
442
443
444
445
    }
    // Update timeline view if necessary
    if (m_ownerId.first == ObjectType::NoItem) {
        // Used for generator clips
        if (!update) emit modelChanged();
    } else {
        // Update fades in timeline
        pCore->updateItemModel(m_ownerId, m_assetId);
446
447
448
449
450
451
        if (!m_isAudio) {
            // Trigger monitor refresh
            pCore->refreshProjectItem(m_ownerId);
            // Invalidate timeline preview
            pCore->invalidateItem(m_ownerId);
        }
452
    }
453
454
}

Nicolas Carion's avatar
linting    
Nicolas Carion committed
455
AssetParameterModel::~AssetParameterModel() = default;
456
457
458

QVariant AssetParameterModel::data(const QModelIndex &index, int role) const
{
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
    if (bypassRoles.contains(role)) {
        switch (role) {
            case InRole:
                return m_asset->get_int("in");
            case OutRole:
                return m_asset->get_int("out");
            case ParentInRole:
                return pCore->getItemIn(m_ownerId);
            case ParentDurationRole:
                if (m_asset->get_int("kdenlive:force_in_out") == 1) {
                    // Zone effect, return effect length
                    return m_asset->get_int("out") - m_asset->get_int("in");
                }
                return pCore->getItemDuration(m_ownerId);
            case ParentPositionRole:
                return pCore->getItemPosition(m_ownerId);
            case HideKeyframesFirstRole:
                return m_hideKeyframesByDefault;
            default:
                qDebug()<<"WARNING; UNHANDLED DATA: "<<role;
                return QVariant();
        }
    }
482
    if (index.row() < 0 || index.row() >= m_rows.size() || !index.isValid()) {
483
484
485
        return QVariant();
    }
    QString paramName = m_rows[index.row()];
486
    Q_ASSERT(m_params.count(paramName) > 0);
487
488
    const QDomElement &element = m_params.at(paramName).xml;
    switch (role) {
489
490
    case Qt::DisplayRole:
    case Qt::EditRole:
491
        return m_params.at(paramName).name;
492
493
    case NameRole:
        return paramName;
494
495
    case TypeRole:
        return QVariant::fromValue<ParamType>(m_params.at(paramName).type);
Nicolas Carion's avatar
Nicolas Carion committed
496
    case CommentRole: {
497
498
499
500
501
502
503
504
        QDomElement commentElem = element.firstChildElement(QStringLiteral("comment"));
        QString comment;
        if (!commentElem.isNull()) {
            comment = i18n(commentElem.text().toUtf8().data());
        }
        return comment;
    }
    case MinRole:
505
        return parseAttribute(m_ownerId, QStringLiteral("min"), element);
506
    case MaxRole:
507
        return parseAttribute(m_ownerId, QStringLiteral("max"), element);
508
    case FactorRole:
509
        return parseAttribute(m_ownerId, QStringLiteral("factor"), element, 1);
510
    case ScaleRole:
511
        return parseAttribute(m_ownerId, QStringLiteral("scale"), element, 0);
512
    case DecimalsRole:
513
        return parseAttribute(m_ownerId, QStringLiteral("decimals"), element);
514
515
    case OddRole:
        return element.attribute(QStringLiteral("odd")) == QLatin1String("1");
516
517
518
519
    case VisualMinRole:
        return parseAttribute(m_ownerId, QStringLiteral("visualmin"), element);
    case VisualMaxRole:
        return parseAttribute(m_ownerId, QStringLiteral("visualmax"), element);
520
    case DefaultRole:
521
        return parseAttribute(m_ownerId, QStringLiteral("default"), element);
522
    case FilterRole:
523
        return parseAttribute(m_ownerId, QStringLiteral("filter"), element);
524
525
    case FilterParamsRole:
        return parseAttribute(m_ownerId, QStringLiteral("filterparams"), element);
526
527
    case FilterConsumerParamsRole:
        return parseAttribute(m_ownerId, QStringLiteral("consumerparams"), element);
528
529
    case FilterJobParamsRole:
        return parseSubAttributes(QStringLiteral("jobparam"), element);
530
531
    case FilterProgressRole:
        return m_filterProgress;
532
533
534
535
536
537
538
    case AlternateNameRole: {
        QDomNode child = element.firstChildElement(QStringLiteral("name"));
        if (child.toElement().hasAttribute(QStringLiteral("conditional"))) {
            return child.toElement().attribute(QStringLiteral("conditional"));
        }
        return m_params.at(paramName).name;
    }
539
540
    case SuffixRole:
        return element.attribute(QStringLiteral("suffix"));
541
542
    case OpacityRole:
        return element.attribute(QStringLiteral("opacity")) != QLatin1String("false");
543
544
    case RelativePosRole:
        return element.attribute(QStringLiteral("relative")) == QLatin1String("true");
545
546
    case ShowInTimelineRole:
        return !element.hasAttribute(QStringLiteral("notintimeline"));
547
548
    case AlphaRole:
        return element.attribute(QStringLiteral("alpha")) == QLatin1String("1");
549
    case ValueRole: {
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
        if (m_params.at(paramName).type == ParamType::MultiSwitch) {
            // Multi params concatenate param names with a '\n' and param values with a space
            QStringList paramNames = paramName.split(QLatin1Char('\n'));
            QStringList values;
            bool valueFound = false;
            for (auto &p : paramNames) {
                const QString val = m_asset->get(p.toUtf8().constData());
                if (!val.isEmpty()) {
                    valueFound = true;
                }
                values << val;
            }
            if (!valueFound) {
                return (element.attribute(QStringLiteral("value")).isNull() ? parseAttribute(m_ownerId, QStringLiteral("default"), element) : element.attribute(QStringLiteral("value")));
            }
            return values.join(QLatin1Char('\n'));
        }
567
        QString value(m_asset->get(paramName.toUtf8().constData()));
568
569
570
571
572
573
574
575
        if (value.isEmpty()) {
            if (element.hasAttribute("default")) {
                return parseAttribute(m_ownerId, QStringLiteral("default"), element);
            } else {
                value = element.attribute(QStringLiteral("value"));
            }
        }
        return value;
576
    }
577
578
    case ListValuesRole:
        return element.attribute(QStringLiteral("paramlist")).split(QLatin1Char(';'));
579
580
    case InstalledValuesRole:
        return m_asset->get("kdenlive:paramlist");
581
582
583
584
    case ListNamesRole: {
        QDomElement namesElem = element.firstChildElement(QStringLiteral("paramlistdisplay"));
        return i18n(namesElem.text().toUtf8().data()).split(QLatin1Char(','));
    }
585
586
587
588
589
590
591
592
593
594
595
596
597
    case ListDependenciesRole: {
        QDomNodeList dependencies = element.elementsByTagName(QStringLiteral("paramdependencies"));
        if (!dependencies.isEmpty()) {
            QDomDocument doc;
            QDomElement d = doc.createElement(QStringLiteral("deps"));
            doc.appendChild(d);
            for (int i = 0; i < dependencies.count(); i++) {
                d.appendChild(doc.importNode(dependencies.at(i), true));
            }
            return doc.toString();
        }
        return QVariant();
    }
598
599
    case NewStuffRole:
        return element.attribute(QStringLiteral("newstuff"));
600
601
    case ModeRole:
        return element.attribute(QStringLiteral("mode"));
602
603
604
605
606
    case List1Role:
        return parseAttribute(m_ownerId, QStringLiteral("list1"), element);
    case List2Role:
        return parseAttribute(m_ownerId, QStringLiteral("list2"), element);
    case Enum1Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
607
        return m_asset->get_double("1");
608
    case Enum2Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
609
        return m_asset->get_double("2");
610
    case Enum3Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
611
        return m_asset->get_double("3");
612
    case Enum4Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
613
        return m_asset->get_double("4");
614
    case Enum5Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
615
        return m_asset->get_double("5");
616
    case Enum6Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
617
        return m_asset->get_double("6");
618
    case Enum7Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
619
        return m_asset->get_double("7");
620
    case Enum8Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
621
        return m_asset->get_double("8");
622
    case Enum9Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
623
        return m_asset->get_double("9");
624
    case Enum10Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
625
        return m_asset->get_double("10");
626
    case Enum11Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
627
        return m_asset->get_double("11");
628
    case Enum12Role:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
629
630
631
632
633
634
635
        return m_asset->get_double("12");
    case Enum13Role:
        return m_asset->get_double("13");
    case Enum14Role:
        return m_asset->get_double("14");
    case Enum15Role:
        return m_asset->get_double("15");
636
637
638
639
    }
    return QVariant();
}

640
641
642
643
644
645
const QString AssetParameterModel::framesToTime(int t) const
{
    return m_asset->frames_to_time(t, mlt_time_clock);
}


646
647
int AssetParameterModel::rowCount(const QModelIndex &parent) const
{
648
    //qDebug() << "===================================================== Requested rowCount" << parent << m_rows.size();
Nicolas Carion's avatar
Nicolas Carion committed
649
    if (parent.isValid()) return 0;
650
651
652
    return m_rows.size();
}

Nicolas Carion's avatar
Nicolas Carion committed
653
654
// static
ParamType AssetParameterModel::paramTypeFromStr(const QString &type)
655
{
Nicolas Carion's avatar
linting    
Nicolas Carion committed
656
    if (type == QLatin1String("double") || type == QLatin1String("float") || type == QLatin1String("constant")) {
657
        return ParamType::Double;
Nicolas Carion's avatar
Nicolas Carion committed
658
659
    }
    if (type == QLatin1String("list")) {
660
        return ParamType::List;
661
    }
662
663
664
    if (type == QLatin1String("listdependency")) {
        return ParamType::ListWithDependency;
    }
665
666
667
    if (type == QLatin1String("urllist")) {
        return ParamType::UrlList;
    }
668
    if (type == QLatin1String("bool")) {
669
        return ParamType::Bool;
Nicolas Carion's avatar
linting    
Nicolas Carion committed
670
671
    }
    if (type == QLatin1String("switch")) {
672
        return ParamType::Switch;
673
674
675
    }
    if (type == QLatin1String("multiswitch")) {
        return ParamType::MultiSwitch;
676
    } else if (type == QLatin1String("simplekeyframe")) {
677
        return ParamType::KeyframeParam;
678
    } else if (type == QLatin1String("animatedrect") || type == QLatin1String("rect")) {
679
        return ParamType::AnimatedRect;
680
681
682
683
    } else if (type == QLatin1String("geometry")) {
        return ParamType::Geometry;
    } else if (type == QLatin1String("addedgeometry")) {
        return ParamType::Addedgeometry;
684
    } else if (type == QLatin1String("keyframe") || type == QLatin1String("animated")) {
685
        return ParamType::KeyframeParam;
686
687
    } else if (type == QLatin1String("color")) {
        return ParamType::Color;
688
689
    } else if (type == QLatin1String("colorwheel")) {
        return ParamType::ColorWheel;
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
    } else if (type == QLatin1String("position")) {
        return ParamType::Position;
    } else if (type == QLatin1String("curve")) {
        return ParamType::Curve;
    } else if (type == QLatin1String("bezier_spline")) {
        return ParamType::Bezier_spline;
    } else if (type == QLatin1String("roto-spline")) {
        return ParamType::Roto_spline;
    } else if (type == QLatin1String("wipe")) {
        return ParamType::Wipe;
    } else if (type == QLatin1String("url")) {
        return ParamType::Url;
    } else if (type == QLatin1String("keywords")) {
        return ParamType::Keywords;
    } else if (type == QLatin1String("fontfamily")) {
        return ParamType::Fontfamily;
    } else if (type == QLatin1String("filterjob")) {
        return ParamType::Filterjob;
    } else if (type == QLatin1String("readonly")) {
        return ParamType::Readonly;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
710
711
    } else if (type == QLatin1String("hidden")) {
        return ParamType::Hidden;
712
    }
Nicolas Carion's avatar
Nicolas Carion committed
713
    qDebug() << "WARNING: Unknown type :" << type;
714
715
716
    return ParamType::Double;
}

717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
// static
QString AssetParameterModel::getDefaultKeyframes(int start, const QString &defaultValue, bool linearOnly)
{
    QString keyframes = QString::number(start);
    if (linearOnly) {
        keyframes.append(QLatin1Char('='));
    } else {
        switch (KdenliveSettings::defaultkeyframeinterp()) {
        case mlt_keyframe_discrete:
            keyframes.append(QStringLiteral("|="));
            break;
        case mlt_keyframe_smooth:
            keyframes.append(QStringLiteral("~="));
            break;
        default:
            keyframes.append(QLatin1Char('='));
            break;
        }
    }
    keyframes.append(defaultValue);
    return keyframes;
}
739
740

// static
Nicolas Carion's avatar
Nicolas Carion committed
741
QVariant AssetParameterModel::parseAttribute(const ObjectId &owner, const QString &attribute, const QDomElement &element, QVariant defaultValue)
742
{
743
    if (!element.hasAttribute(attribute) && !defaultValue.isNull()) {
744
745
746
747
        return defaultValue;
    }
    ParamType type = paramTypeFromStr(element.attribute(QStringLiteral("type")));
    QString content = element.attribute(attribute);
748
749
750
    std::unique_ptr<ProfileModel> &profile = pCore->getCurrentProfile();
    int width = profile->width();
    int height = profile->height();
751
752
    QSize frameSize = pCore->getItemFrameSize(owner);
    if(type == ParamType::AnimatedRect && content == "adjustcenter" && !frameSize.isEmpty()) {
753
754
755
756
757
        int contentHeight;
        int contentWidth;
        double sourceDar = frameSize.width() / frameSize.height();
        if (sourceDar > pCore->getCurrentDar()) {
            // Fit to width
Vincent Pinon's avatar
Vincent Pinon committed
758
759
            double factor = double(width) / frameSize.width() * pCore->getCurrentSar();
            contentHeight = int(height * factor + 0.5);
760
761
762
            contentWidth = width;
        } else {
            // Fit to height
Vincent Pinon's avatar
Vincent Pinon committed
763
            double factor = double(height) / frameSize.height();
764
            contentHeight = height;
Vincent Pinon's avatar
Vincent Pinon committed
765
            contentWidth =int(frameSize.width() / pCore->getCurrentSar() * factor + 0.5);
766
767
768
769
        }
        // Center
        content = QString("%1 %2 %3 %4").arg((width - contentWidth) / 2).arg((height - contentHeight) / 2).arg(contentWidth).arg(contentHeight);
    } else if (content.contains(QLatin1Char('%'))) {
770
        int in = pCore->getItemIn(owner);
771
        int out = in + pCore->getItemDuration(owner) - 1;
772
        int frame_duration = pCore->getDurationFromString(KdenliveSettings::fade_duration());
773
774
775
776
        // replace symbols in the double parameter
        content.replace(QLatin1String("%maxWidth"), QString::number(width))
            .replace(QLatin1String("%maxHeight"), QString::number(height))
            .replace(QLatin1String("%width"), QString::number(width))
777
            .replace(QLatin1String("%height"), QString::number(height))
778
779
            .replace(QLatin1String("%out"), QString::number(out))
            .replace(QLatin1String("%fade"), QString::number(frame_duration));
780
        if ((type == ParamType::AnimatedRect || type == ParamType::Geometry) && attribute == QLatin1String("default")) {
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
            if (content.contains(QLatin1Char('%'))) {
                // This is a generic default like: "25% 0% 50% 100%". Parse values
                QStringList numbers = content.split(QLatin1Char(' '));
                content.clear();
                int ix = 0;
                for ( QString &val : numbers) {
                    if (val.endsWith(QLatin1Char('%'))) {
                        val.chop(1);
                        double n = val.toDouble()/100.;
                        if (ix %2 == 0) {
                            n *= width;
                        } else {
                            n *= height;
                        }
                        ix++;
                        content.append(QString("%1 ").arg(qRound(n)));
                    } else {
                        content.append(QString("%1 ").arg(val));
                    }
                }
            }
        }
        else if (type == ParamType::Double || type == ParamType::Hidden) {
804
805
            // Use a Mlt::Properties to parse mathematical operators
            Mlt::Properties p;
806
            p.set("eval", content.prepend(QLatin1Char('@')).toLatin1().constData());
807
808
            return p.get_double("eval");
        }
809
    } else if (type == ParamType::Double || type == ParamType::Hidden) {
810
811
812
        if (attribute == QLatin1String("default")) {
            return content.toDouble();
        }
Simon Eugster's avatar
Simon Eugster committed
813
814
815
816
817
818
        bool ok;
        double converted = content.toDouble(&ok);
        if (!ok) {
            qDebug() << "QLocale: Could not load double parameter" << content;
        }
        return converted;
819
    }
820
821
822
    if (attribute == QLatin1String("default")) {
        if (type == ParamType::RestrictedAnim) {
            content = getDefaultKeyframes(0, content, true);
823
824
825
826
827
828
829
830
        } else if (type == ParamType::KeyframeParam) {
            return content.toDouble();
        } else if (type == ParamType::List) {
            bool ok;
            double res = content.toDouble(&ok);
            if (ok) {
                return res;
            }
831
            return defaultValue.isNull() ? content : defaultValue;
832
833
        }
    }
834
835
836
    return content;
}

837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
QVariant AssetParameterModel::parseSubAttributes(const QString &attribute, const QDomElement &element) const
{
    QDomNodeList nodeList = element.elementsByTagName(attribute);
    if (nodeList.isEmpty()) {
        return QVariant();
    }
    QVariantList jobDataList;
    for (int i = 0; i < nodeList.count(); ++i) {
        QDomElement currentParameter = nodeList.item(i).toElement();
        QStringList jobData {currentParameter.attribute(QStringLiteral("name")), currentParameter.text()};
        jobDataList << jobData;
    }
    return jobDataList;
}

852
QString AssetParameterModel::getAssetId() const
853
854
855
{
    return m_assetId;
}
856

857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
const QString AssetParameterModel::getAssetMltId()
{
    return m_asset->get("id");
}

void AssetParameterModel::setActive(bool active)
{
    m_active = active;
}

bool AssetParameterModel::isActive() const
{
    return m_active;
}

Nicolas Carion's avatar
Nicolas Carion committed
872
QVector<QPair<QString, QVariant>> AssetParameterModel::getAllParameters() const
873
{
Nicolas Carion's avatar
Nicolas Carion committed
874
    QVector<QPair<QString, QVariant>> res;
Vincent Pinon's avatar
Vincent Pinon committed
875
    res.reserve(int(m_fixedParams.size() + m_params.size()));
Nicolas Carion's avatar
Nicolas Carion committed
876
    for (const auto &fixed : m_fixedParams) {
877
878
879
        res.push_back(QPair<QString, QVariant>(fixed.first, fixed.second));
    }

Nicolas Carion's avatar
Nicolas Carion committed
880
    for (const auto &param : m_params) {
881
882
883
        if (!param.first.isEmpty()) {
            res.push_back(QPair<QString, QVariant>(param.first, param.second.value));
        }
884
885
886
887
    }
    return res;
}

888
QJsonDocument AssetParameterModel::toJson(bool includeFixed) const
889
890
{
    QJsonArray list;
891
892
893
894
895
    if (includeFixed) {
        for (const auto &fixed : m_fixedParams) {
            QJsonObject currentParam;
            QModelIndex ix = index(m_rows.indexOf(fixed.first), 0);
            currentParam.insert(QLatin1String("name"), QJsonValue(fixed.first));
896
            currentParam.insert(QLatin1String("value"), fixed.second.type() == QVariant::Double ? QJsonValue(fixed.second.toDouble()) : QJsonValue(fixed.second.toString()));
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
            int type = data(ix, AssetParameterModel::TypeRole).toInt();
            double min = data(ix, AssetParameterModel::MinRole).toDouble();
            double max = data(ix, AssetParameterModel::MaxRole).toDouble();
            double factor = data(ix, AssetParameterModel::FactorRole).toDouble();
            int in = data(ix, AssetParameterModel::ParentInRole).toInt();
            int out = in + data(ix, AssetParameterModel::ParentDurationRole).toInt();
            if (factor > 0) {
                min /= factor;
                max /= factor;
            }
            currentParam.insert(QLatin1String("type"), QJsonValue(type));
            currentParam.insert(QLatin1String("min"), QJsonValue(min));
            currentParam.insert(QLatin1String("max"), QJsonValue(max));
            currentParam.insert(QLatin1String("in"), QJsonValue(in));
            currentParam.insert(QLatin1String("out"), QJsonValue(out));
            list.push_back(currentParam);
913
914
915
        }
    }

916
    QString x, y, w, h;
917
    int rectIn = 0, rectOut = 0;
918
    for (const auto &param : m_params) {
919
        if (!includeFixed && param.second.type != ParamType::KeyframeParam && param.second.type != ParamType::AnimatedRect && param.second.type != ParamType::Roto_spline) {
920
921
            continue;
        }
922
923
        QJsonObject currentParam;
        QModelIndex ix = index(m_rows.indexOf(param.first), 0);
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939

        if(param.first.contains("Position X")) {
            x = param.second.value.toString();
            rectIn = data(ix, AssetParameterModel::ParentInRole).toInt();
            rectOut = rectIn + data(ix, AssetParameterModel::ParentDurationRole).toInt();
        }
        if(param.first.contains("Position Y")) {
            y = param.second.value.toString();
        }
        if(param.first.contains("Size X")) {
            w = param.second.value.toString();
        }
        if(param.first.contains("Size Y")) {
            h = param.second.value.toString();
        }

940
        currentParam.insert(QLatin1String("name"), QJsonValue(param.first));
941
        currentParam.insert(QLatin1String("DisplayName"), QJsonValue(param.second.name));
942
        currentParam.insert(QLatin1String("value"), param.second.value.type() == QVariant::Double ? QJsonValue(param.second.value.toDouble()) : QJsonValue(param.second.value.toString()));
943
944
945
946
        int type = data(ix, AssetParameterModel::TypeRole).toInt();
        double min = data(ix, AssetParameterModel::MinRole).toDouble();
        double max = data(ix, AssetParameterModel::MaxRole).toDouble();
        double factor = data(ix, AssetParameterModel::FactorRole).toDouble();
947
948
        int in = data(ix, AssetParameterModel::ParentInRole).toInt();
        int out = in + data(ix, AssetParameterModel::ParentDurationRole).toInt();
949
        bool opacity = data(ix, AssetParameterModel::OpacityRole).toBool();
950
951
952
953
954
955
956
        if (factor > 0) {
            min /= factor;
            max /= factor;
        }
        currentParam.insert(QLatin1String("type"), QJsonValue(type));
        currentParam.insert(QLatin1String("min"), QJsonValue(min));
        currentParam.insert(QLatin1String("max"), QJsonValue(max));
957
958
        currentParam.insert(QLatin1String("in"), QJsonValue(in));
        currentParam.insert(QLatin1String("out"), QJsonValue(out));
959
        currentParam.insert(QLatin1String("opacity"), QJsonValue(opacity));
960
961
        list.push_back(currentParam);
    }
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
    if(!(x.isEmpty() || y.isEmpty() || w.isEmpty() || h.isEmpty())) {
        QJsonObject currentParam;
        currentParam.insert(QLatin1String("name"), QStringLiteral("rect"));
        int size = x.split(";").length();
        QString value;
        for(int i = 0; i < size; i++) {
            QSize frameSize = pCore->getCurrentFrameSize();
            QString pos = x.split(";").at(i).split("=").at(0);
            double xval = x.split(";").at(i).split("=").at(1).toDouble();
            xval = xval * frameSize.width();
            double yval = y.split(";").at(i).split("=").at(1).toDouble();
            yval = yval * frameSize.height();
            double wval = w.split(";").at(i).split("=").at(1).toDouble();
            wval = wval * frameSize.width() * 2;
            double hval = h.split(";").at(i).split("=").at(1).toDouble();
            hval = hval * frameSize.height() * 2;
            value.append(QString("%1=%2 %3 %4 %5 1;").arg(pos).arg(int(xval - wval/2)).arg(int(yval - hval/2)).arg(int(wval)).arg(int(hval)));
        }
        currentParam.insert(QLatin1String("value"), value);
        currentParam.insert(QLatin1String("type"), QJsonValue(int(ParamType::AnimatedRect)));
        currentParam.insert(QLatin1String("min"), QJsonValue(0));
        currentParam.insert(QLatin1String("max"), QJsonValue(0));
        currentParam.insert(QLatin1String("in"), QJsonValue(rectIn));
        currentParam.insert(QLatin1String("out"), QJsonValue(rectOut));
        list.push_front(currentParam);
    }
988
989
    return QJsonDocument(list);
}
990
991
992
993
994
995
996
997
998
999
1000

QJsonDocument AssetParameterModel::valueAsJson(int pos, bool includeFixed) const
{
    QJsonArray list;
    if(!m_keyframes) {
        return QJsonDocument(list);
    }

    if (includeFixed) {
        for (const auto &fixed : m_fixedParams) {
            QJsonObject currentParam;