keyframemodel.cpp 45.4 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 "keyframemodel.hpp"
#include "core.h"
Nicolas Carion's avatar
Nicolas Carion committed
24
#include "doc/docundostack.hpp"
25
#include "macros.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
26
#include "profiles/profilemodel.hpp"
27
28
#include "rotoscoping/bpoint.h"
#include "rotoscoping/rotohelper.hpp"
29

Laurent Montel's avatar
Laurent Montel committed
30
31
#include <QSize>
#include <QLineF>
32
#include <QDebug>
33
#include <QJsonDocument>
34
#include <mlt++/Mlt.h>
Nicolas Carion's avatar
Nicolas Carion committed
35
#include <utility>
36

37
KeyframeModel::KeyframeModel(std::weak_ptr<AssetParameterModel> model, const QModelIndex &index, std::weak_ptr<DocUndoStack> undo_stack, QObject *parent)
38
    : QAbstractListModel(parent)
39
    , m_model(std::move(model))
40
    , m_undoStack(std::move(undo_stack))
41
    , m_index(index)
42
    , m_lastData()
43
44
    , m_lock(QReadWriteLock::Recursive)
{
Nicolas Carion's avatar
Nicolas Carion committed
45
    qDebug() << "Construct keyframemodel. Checking model:" << m_model.expired();
46
47
48
    if (auto ptr = m_model.lock()) {
        m_paramType = ptr->data(m_index, AssetParameterModel::TypeRole).value<ParamType>();
    }
49
    setup();
50
    refresh();
51
52
53
54
55
56
57
58
59
60
61
62
63
}

void KeyframeModel::setup()
{
    // We connect the signals of the abstractitemmodel to a more generic one.
    connect(this, &KeyframeModel::columnsMoved, this, &KeyframeModel::modelChanged);
    connect(this, &KeyframeModel::columnsRemoved, this, &KeyframeModel::modelChanged);
    connect(this, &KeyframeModel::columnsInserted, this, &KeyframeModel::modelChanged);
    connect(this, &KeyframeModel::rowsMoved, this, &KeyframeModel::modelChanged);
    connect(this, &KeyframeModel::rowsRemoved, this, &KeyframeModel::modelChanged);
    connect(this, &KeyframeModel::rowsInserted, this, &KeyframeModel::modelChanged);
    connect(this, &KeyframeModel::modelReset, this, &KeyframeModel::modelChanged);
    connect(this, &KeyframeModel::dataChanged, this, &KeyframeModel::modelChanged);
64
    connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
65
66
}

67
bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value, bool notify, Fun &undo, Fun &redo)
68
{
Nicolas Carion's avatar
Nicolas Carion committed
69
    qDebug() << "ADD keyframe" << pos.frames(pCore->getCurrentFps()) << value << notify;
70
71
72
73
    QWriteLocker locker(&m_lock);
    Fun local_undo = []() { return true; };
    Fun local_redo = []() { return true; };
    if (m_keyframeList.count(pos) > 0) {
Nicolas Carion's avatar
Nicolas Carion committed
74
        qDebug() << "already there";
75
        if (std::pair<KeyframeType, QVariant>({type, value}) == m_keyframeList.at(pos)) {
Nicolas Carion's avatar
Nicolas Carion committed
76
            qDebug() << "nothing to do";
77
78
            return true; // nothing to do
        }
79
80
        // In this case we simply change the type and value
        KeyframeType oldType = m_keyframeList[pos].first;
81
        QVariant oldValue = m_keyframeList[pos].second;
82
83
        local_undo = updateKeyframe_lambda(pos, oldType, oldValue, notify);
        local_redo = updateKeyframe_lambda(pos, type, value, notify);
84
    } else {
85
86
        local_redo = addKeyframe_lambda(pos, type, value, notify);
        local_undo = deleteKeyframe_lambda(pos, notify);
87
88
89
90
91
92
93
94
    }
    if (local_redo()) {
        UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
        return true;
    }
    return false;
}

95
96
bool KeyframeModel::addKeyframe(int frame, double normalizedValue)
{
97
98
    QVariant result = getNormalizedValue(normalizedValue);
    if (result.isValid()) {
Nicolas Carion's avatar
Nicolas Carion committed
99
        // TODO: Use default configurable kf type
100
        return addKeyframe(GenTime(frame, pCore->getCurrentFps()), KeyframeType::Linear, result);
101
102
103
104
    }
    return false;
}

105
bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value)
106
107
108
109
110
{
    QWriteLocker locker(&m_lock);
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };

111
    bool update = (m_keyframeList.count(pos) > 0);
Nicolas Carion's avatar
Nicolas Carion committed
112
    bool res = addKeyframe(pos, type, std::move(value), true, undo, redo);
113
    if (res) {
114
        PUSH_UNDO(undo, redo, update ? i18n("Change keyframe type") : i18n("Add keyframe"));
115
116
117
118
    }
    return res;
}

119
bool KeyframeModel::removeKeyframe(GenTime pos, Fun &undo, Fun &redo, bool notify)
120
{
121
    qDebug() << "Going to remove keyframe at " << pos.frames(pCore->getCurrentFps()) << " NOTIFY: " << notify;
Nicolas Carion's avatar
Nicolas Carion committed
122
    qDebug() << "before" << getAnimProperty();
123
124
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_keyframeList.count(pos) > 0);
125
    KeyframeType oldType = m_keyframeList[pos].first;
126
    QVariant oldValue = m_keyframeList[pos].second;
127
    Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue, notify);
128
    Fun local_redo = deleteKeyframe_lambda(pos, notify);
129
    if (local_redo()) {
130
        qDebug() << "after" << getAnimProperty();
131
132
133
134
135
136
        UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
        return true;
    }
    return false;
}

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
bool KeyframeModel::duplicateKeyframe(GenTime srcPos, GenTime dstPos, Fun &undo, Fun &redo)
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_keyframeList.count(srcPos) > 0);
    KeyframeType oldType = m_keyframeList[srcPos].first;
    QVariant oldValue = m_keyframeList[srcPos].second;
    Fun local_redo = addKeyframe_lambda(dstPos, oldType, oldValue, true);
    Fun local_undo = deleteKeyframe_lambda(dstPos, true);
    if (local_redo()) {
        UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
        return true;
    }
    return false;
}

152
153
154
155
156
157
bool KeyframeModel::removeKeyframe(int frame)
{
    GenTime pos(frame, pCore->getCurrentFps());
    return removeKeyframe(pos);
}

158
159
160
161
162
163
bool KeyframeModel::removeKeyframe(GenTime pos)
{
    QWriteLocker locker(&m_lock);
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };

164
    if (m_keyframeList.count(pos) > 0 && m_keyframeList.find(pos) == m_keyframeList.begin()) {
Nicolas Carion's avatar
Nicolas Carion committed
165
        return false; // initial point must stay
166
167
    }

168
169
170
171
172
173
174
    bool res = removeKeyframe(pos, undo, redo);
    if (res) {
        PUSH_UNDO(undo, redo, i18n("Delete keyframe"));
    }
    return res;
}

175
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, Fun &undo, Fun &redo)
176
{
Nicolas Carion's avatar
Nicolas Carion committed
177
    qDebug() << "starting to move keyframe" << oldPos.frames(pCore->getCurrentFps()) << pos.frames(pCore->getCurrentFps());
178
179
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_keyframeList.count(oldPos) > 0);
180
    if (oldPos == pos) {
181
182
183
184
185
186
187
        if (!newVal.isValid()) {
            // no change
            return true;
        }
        if (m_paramType == ParamType::AnimatedRect) {
            return updateKeyframe(pos, newVal);
        }
188
        // Calculate real value from normalized
189
190
        QVariant result = getNormalizedValue(newVal.toDouble());
        return updateKeyframe(pos, result);
191
    }
192
    if (oldPos != pos && hasKeyframe(pos)) {
193
194
        // Move rejected, another keyframe is here
        qDebug()<<"==== MOVE REJECTED!!";
195
196
        return false;
    }
197
    KeyframeType oldType = m_keyframeList[oldPos].first;
198
    QVariant oldValue = m_keyframeList[oldPos].second;
199
200
    Fun local_undo = []() { return true; };
    Fun local_redo = []() { return true; };
Nicolas Carion's avatar
Nicolas Carion committed
201
    qDebug() << getAnimProperty();
202
    // TODO: use the new Animation::key_set_frame to move a keyframe
203
    bool res = removeKeyframe(oldPos, local_undo, local_redo);
Nicolas Carion's avatar
Nicolas Carion committed
204
    qDebug() << "Move keyframe finished deletion:" << res;
Nicolas Carion's avatar
Nicolas Carion committed
205
    qDebug() << getAnimProperty();
206
    if (res) {
207
208
209
210
211
212
        if (m_paramType == ParamType::AnimatedRect) {
            if (!newVal.isValid()) {
                newVal = oldValue;
            }
            res = addKeyframe(pos, oldType, newVal, true, local_undo, local_redo);
        } else if (newVal.isValid()) {
213
214
215
            QVariant result = getNormalizedValue(newVal.toDouble());
            if (result.isValid()) {
                res = addKeyframe(pos, oldType, result, true, local_undo, local_redo);
216
217
218
219
            }
        } else {
            res = addKeyframe(pos, oldType, oldValue, true, local_undo, local_redo);
        }
Nicolas Carion's avatar
Nicolas Carion committed
220
        qDebug() << "Move keyframe finished insertion:" << res;
Nicolas Carion's avatar
Nicolas Carion committed
221
        qDebug() << getAnimProperty();
222
223
    }
    if (res) {
224
        UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
225
    } else {
226
        bool undone = local_undo();
227
228
229
230
231
        Q_ASSERT(undone);
    }
    return res;
}

232
233
234
235
bool KeyframeModel::moveKeyframe(int oldPos, int pos, bool logUndo)
{
    GenTime oPos(oldPos, pCore->getCurrentFps());
    GenTime nPos(pos, pCore->getCurrentFps());
236
    return moveKeyframe(oPos, nPos, QVariant(), logUndo);
237
238
}

239
240
241
242
243
244
245
246
247
bool KeyframeModel::offsetKeyframes(int oldPos, int pos, bool logUndo)
{
    if (oldPos == pos) return true;
    GenTime oldFrame(oldPos, pCore->getCurrentFps());
    Q_ASSERT(m_keyframeList.count(oldFrame) > 0);
    GenTime diff(pos - oldPos, pCore->getCurrentFps());
    QWriteLocker locker(&m_lock);
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
Nicolas Carion's avatar
Nicolas Carion committed
248
    QList<GenTime> times;
249
250
251
252
    for (const auto &m : m_keyframeList) {
        if (m.first < oldFrame) continue;
        times << m.first;
    }
253
    bool res = true;
Vincent Pinon's avatar
Vincent Pinon committed
254
    for (const auto &t : qAsConst(times)) {
255
        res &= moveKeyframe(t, t + diff, QVariant(), undo, redo);
256
257
    }
    if (res && logUndo) {
258
        PUSH_UNDO(undo, redo, i18nc("@action", "Move keyframes"));
259
260
261
262
    }
    return res;
}

263
bool KeyframeModel::moveKeyframe(int oldPos, int pos, QVariant newVal)
264
265
266
{
    GenTime oPos(oldPos, pCore->getCurrentFps());
    GenTime nPos(pos, pCore->getCurrentFps());
Nicolas Carion's avatar
Nicolas Carion committed
267
    return moveKeyframe(oPos, nPos, std::move(newVal), true);
268
269
}

270
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, bool logUndo)
271
272
273
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_keyframeList.count(oldPos) > 0);
Nicolas Carion's avatar
Nicolas Carion committed
274
    if (oldPos == pos) return true;
275
276
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
Nicolas Carion's avatar
Nicolas Carion committed
277
    bool res = moveKeyframe(oldPos, pos, std::move(newVal), undo, redo);
278
    if (res && logUndo) {
279
        PUSH_UNDO(undo, redo, i18nc("@action", "Move keyframe"));
280
281
282
283
    }
    return res;
}

284
285
286
287
288
bool KeyframeModel::directUpdateKeyframe(GenTime pos, QVariant value)
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_keyframeList.count(pos) > 0);
    KeyframeType type = m_keyframeList[pos].first;
Nicolas Carion's avatar
Nicolas Carion committed
289
    auto operation = updateKeyframe_lambda(pos, type, std::move(value), true);
290
291
292
    return operation();
}

Nicolas Carion's avatar
Nicolas Carion committed
293
bool KeyframeModel::updateKeyframe(GenTime pos, const QVariant &value, Fun &undo, Fun &redo, bool update)
294
295
296
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_keyframeList.count(pos) > 0);
297
    KeyframeType type = m_keyframeList[pos].first;
298
    QVariant oldValue = m_keyframeList[pos].second;
299
    // Check if keyframe is different
300
    if (m_paramType == ParamType::KeyframeParam) {
301
        if (qFuzzyCompare(oldValue.toDouble(), value.toDouble())) return true;
302
    }
303
304
    auto operation = updateKeyframe_lambda(pos, type, value, update);
    auto reverse = updateKeyframe_lambda(pos, type, oldValue, update);
305
306
307
308
309
310
311
    bool res = operation();
    if (res) {
        UPDATE_UNDO_REDO(operation, reverse, undo, redo);
    }
    return res;
}

312
313
314
315
bool KeyframeModel::updateKeyframe(int pos, double newVal)
{
    GenTime Pos(pos, pCore->getCurrentFps());
    if (auto ptr = m_model.lock()) {
316
317
318
319
320
321
        double min = ptr->data(m_index, AssetParameterModel::VisualMinRole).toDouble();
        double max = ptr->data(m_index, AssetParameterModel::VisualMaxRole).toDouble();
        if (qFuzzyIsNull(min) && qFuzzyIsNull(max)) {
            min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
            max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
        }
322
323
324
325
326
        double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
        double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
        int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
        double realValue;
        if (logRole == -1) {
327
            // Logarythmic scale
328
            if (newVal >= 0.5) {
329
                realValue = norm + pow(2 * (newVal - 0.5), 10.0 / 6) * (max / factor - norm);
330
331
332
333
334
335
336
337
338
339
340
            } else {
                realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
            }
        } else {
            realValue = (newVal * (max - min) + min) / factor;
        }
        return updateKeyframe(Pos, realValue);
    }
    return false;
}

341
bool KeyframeModel::updateKeyframe(GenTime pos, QVariant value)
342
343
344
345
346
347
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_keyframeList.count(pos) > 0);

    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
Nicolas Carion's avatar
Nicolas Carion committed
348
    bool res = updateKeyframe(pos, std::move(value), undo, redo);
349
350
351
352
353
354
    if (res) {
        PUSH_UNDO(undo, redo, i18n("Update keyframe"));
    }
    return res;
}

355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
KeyframeType convertFromMltType(mlt_keyframe_type type)
{
    switch (type) {
    case mlt_keyframe_linear:
        return KeyframeType::Linear;
    case mlt_keyframe_discrete:
        return KeyframeType::Discrete;
    case mlt_keyframe_smooth:
        return KeyframeType::Curve;
    }
    return KeyframeType::Linear;
}

bool KeyframeModel::updateKeyframeType(GenTime pos, int type, Fun &undo, Fun &redo)
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_keyframeList.count(pos) > 0);
    KeyframeType oldType = m_keyframeList[pos].first;
373
    KeyframeType newType = convertFromMltType(mlt_keyframe_type(type));
374
375
376
377
378
379
380
381
382
383
384
385
386
387
    QVariant value = m_keyframeList[pos].second;
    // Check if keyframe is different
    if (m_paramType == ParamType::KeyframeParam) {
        if (oldType == newType) return true;
    }
    auto operation = updateKeyframe_lambda(pos, newType, value, true);
    auto reverse = updateKeyframe_lambda(pos, oldType, value, true);
    bool res = operation();
    if (res) {
        UPDATE_UNDO_REDO(operation, reverse, undo, redo);
    }
    return res;
}

Nicolas Carion's avatar
Nicolas Carion committed
388
Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, const QVariant &value, bool notify)
389
390
{
    QWriteLocker locker(&m_lock);
391
    return [this, pos, type, value, notify]() {
392
        qDebug() << "update lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
393
394
        Q_ASSERT(m_keyframeList.count(pos) > 0);
        int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
395
396
        m_keyframeList[pos].first = type;
        m_keyframeList[pos].second = value;
Nicolas Carion's avatar
Nicolas Carion committed
397
        if (notify) emit dataChanged(index(row), index(row), {ValueRole, NormalizedValueRole, TypeRole});
398
399
400
401
        return true;
    };
}

Nicolas Carion's avatar
Nicolas Carion committed
402
Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type, const QVariant &value, bool notify)
403
404
{
    QWriteLocker locker(&m_lock);
405
    return [this, notify, pos, type, value]() {
Nicolas Carion's avatar
Nicolas Carion committed
406
        qDebug() << "add lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
407
408
409
410
411
412
413
        Q_ASSERT(m_keyframeList.count(pos) == 0);
        // We determine the row of the newly added marker
        auto insertionIt = m_keyframeList.lower_bound(pos);
        int insertionRow = static_cast<int>(m_keyframeList.size());
        if (insertionIt != m_keyframeList.end()) {
            insertionRow = static_cast<int>(std::distance(m_keyframeList.begin(), insertionIt));
        }
Nicolas Carion's avatar
Nicolas Carion committed
414
        if (notify) beginInsertRows(QModelIndex(), insertionRow, insertionRow);
415
416
        m_keyframeList[pos].first = type;
        m_keyframeList[pos].second = value;
Nicolas Carion's avatar
Nicolas Carion committed
417
        if (notify) endInsertRows();
418
419
420
421
        return true;
    };
}

422
Fun KeyframeModel::deleteKeyframe_lambda(GenTime pos, bool notify)
423
424
{
    QWriteLocker locker(&m_lock);
425
    return [this, pos, notify]() {
Nicolas Carion's avatar
Nicolas Carion committed
426
427
        qDebug() << "delete lambda" << pos.frames(pCore->getCurrentFps()) << notify;
        qDebug() << "before" << getAnimProperty();
428
        Q_ASSERT(m_keyframeList.count(pos) > 0);
429
        //Q_ASSERT(pos != GenTime()); // cannot delete initial point
430
        int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
Nicolas Carion's avatar
Nicolas Carion committed
431
        if (notify) beginRemoveRows(QModelIndex(), row, row);
432
        m_keyframeList.erase(pos);
Nicolas Carion's avatar
Nicolas Carion committed
433
434
        if (notify) endRemoveRows();
        qDebug() << "after" << getAnimProperty();
435
436
437
438
439
440
441
442
443
444
        return true;
    };
}

QHash<int, QByteArray> KeyframeModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[PosRole] = "position";
    roles[FrameRole] = "frame";
    roles[TypeRole] = "type";
445
    roles[ValueRole] = "value";
446
    roles[NormalizedValueRole] = "normalizedValue";
447
448
449
450
451
452
453
454
455
456
457
458
459
460
    return roles;
}

QVariant KeyframeModel::data(const QModelIndex &index, int role) const
{
    READ_LOCK();
    if (index.row() < 0 || index.row() >= static_cast<int>(m_keyframeList.size()) || !index.isValid()) {
        return QVariant();
    }
    auto it = m_keyframeList.begin();
    std::advance(it, index.row());
    switch (role) {
    case Qt::DisplayRole:
    case Qt::EditRole:
461
462
    case ValueRole:
        return it->second.second;
463
    case NormalizedValueRole: {
464
465
        if (m_paramType == ParamType::AnimatedRect) {
            const QString &data = it->second.second.toString();
Simon Eugster's avatar
Simon Eugster committed
466
467
468
469
470
            bool ok;
            double converted = data.section(QLatin1Char(' '), -1).toDouble(&ok);
            if (!ok) {
                qDebug() << "QLocale: Could not convert animated rect opacity" << data;
            }
471
472
473
474
475
            if (auto ptr = m_model.lock()) {
                if (ptr->getAssetId() != QLatin1String("qtblend")) {
                    converted /= 100.;
                }
            }
Simon Eugster's avatar
Simon Eugster committed
476
            return converted;
477
        }
478
479
480
        double val = it->second.second.toDouble();
        if (auto ptr = m_model.lock()) {
            Q_ASSERT(m_index.isValid());
481
482
483
484
485
486
            double min = ptr->data(m_index, AssetParameterModel::VisualMinRole).toDouble();
            double max = ptr->data(m_index, AssetParameterModel::VisualMaxRole).toDouble();
            if (qFuzzyIsNull(min) && qFuzzyIsNull(max)) {
                min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
                max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
            }
487
            double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
488
489
490
491
            double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
            int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
            double linear = val * factor;
            if (logRole == -1) {
492
493
                // Logarythmic scale
                // transform current value to 0..1 scale
494
                if (linear >= norm) {
495
496
                    double scaled = (linear - norm) / (max * factor - norm);
                    return 0.5 + pow(scaled, 0.6) * 0.5;
497
498
499
500
501
502
                }
                double scaled = (linear - norm) / (min * factor - norm);
                // Log scale
                return 0.5 - pow(scaled, 0.6) * 0.5;
            }
            return (linear - min) / (max - min);
503
        } else {
Nicolas Carion's avatar
Nicolas Carion committed
504
            qDebug() << "// CANNOT LOCK effect MODEL";
505
506
507
        }
        return 1;
    }
508
509
510
511
512
513
    case PosRole:
        return it->first.seconds();
    case FrameRole:
    case Qt::UserRole:
        return it->first.frames(pCore->getCurrentFps());
    case TypeRole:
514
        return QVariant::fromValue<KeyframeType>(it->second.first);
515
516
517
518
519
520
521
522
523
524
525
    }
    return QVariant();
}

int KeyframeModel::rowCount(const QModelIndex &parent) const
{
    READ_LOCK();
    if (parent.isValid()) return 0;
    return static_cast<int>(m_keyframeList.size());
}

526
527
528
529
530
531
bool KeyframeModel::singleKeyframe() const
{
    READ_LOCK();
    return m_keyframeList.size() <= 1;
}

532
533
534
535
536
537
Keyframe KeyframeModel::getKeyframe(const GenTime &pos, bool *ok) const
{
    READ_LOCK();
    if (m_keyframeList.count(pos) <= 0) {
        // return empty marker
        *ok = false;
538
        return {GenTime(), KeyframeType::Linear};
539
540
    }
    *ok = true;
541
    return {pos, m_keyframeList.at(pos).first};
542
543
}

544
545
546
547
548
549
Keyframe KeyframeModel::getNextKeyframe(const GenTime &pos, bool *ok) const
{
    auto it = m_keyframeList.upper_bound(pos);
    if (it == m_keyframeList.end()) {
        // return empty marker
        *ok = false;
550
        return {GenTime(), KeyframeType::Linear};
551
552
    }
    *ok = true;
553
    return {(*it).first, (*it).second.first};
554
555
556
557
558
559
560
561
}

Keyframe KeyframeModel::getPrevKeyframe(const GenTime &pos, bool *ok) const
{
    auto it = m_keyframeList.lower_bound(pos);
    if (it == m_keyframeList.begin()) {
        // return empty marker
        *ok = false;
562
        return {GenTime(), KeyframeType::Linear};
563
564
565
    }
    --it;
    *ok = true;
566
    return {(*it).first, (*it).second.first};
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
}

Keyframe KeyframeModel::getClosestKeyframe(const GenTime &pos, bool *ok) const
{
    if (m_keyframeList.count(pos) > 0) {
        return getKeyframe(pos, ok);
    }
    bool ok1, ok2;
    auto next = getNextKeyframe(pos, &ok1);
    auto prev = getPrevKeyframe(pos, &ok2);
    *ok = ok1 || ok2;
    if (ok1 && ok2) {
        double fps = pCore->getCurrentFps();
        if (qAbs(next.first.frames(fps) - pos.frames(fps)) < qAbs(prev.first.frames(fps) - pos.frames(fps))) {
            return next;
        }
        return prev;
    } else if (ok1) {
        return next;
    } else if (ok2) {
        return prev;
    }
    // return empty marker
590
    return {GenTime(), KeyframeType::Linear};
591
592
}

593
bool KeyframeModel::hasKeyframe(int frame) const
594
595
596
597
{
    return hasKeyframe(GenTime(frame, pCore->getCurrentFps()));
}
bool KeyframeModel::hasKeyframe(const GenTime &pos) const
598
599
{
    READ_LOCK();
600
    return m_keyframeList.count(pos) > 0;
601
602
}

603
bool KeyframeModel::removeAllKeyframes(Fun &undo, Fun &redo)
604
605
606
607
608
{
    QWriteLocker locker(&m_lock);
    std::vector<GenTime> all_pos;
    Fun local_undo = []() { return true; };
    Fun local_redo = []() { return true; };
609
    int kfrCount = int(m_keyframeList.size()) - 1;
Nicolas Carion's avatar
Nicolas Carion committed
610
611
612
613
614
    if (kfrCount <= 0) {
        // Nothing to do
        UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
        return true;
    }
615
616
    // we trigger only one global remove/insertrow event
    Fun update_redo_start = [this, kfrCount]() {
617
        beginRemoveRows(QModelIndex(), 1, kfrCount);
618
619
620
621
622
623
624
        return true;
    };
    Fun update_redo_end = [this]() {
        endRemoveRows();
        return true;
    };
    Fun update_undo_start = [this, kfrCount]() {
625
        beginInsertRows(QModelIndex(), 1, kfrCount);
626
627
628
629
630
631
632
633
        return true;
    };
    Fun update_undo_end = [this]() {
        endInsertRows();
        return true;
    };
    PUSH_LAMBDA(update_redo_start, local_redo);
    PUSH_LAMBDA(update_undo_start, local_undo);
Nicolas Carion's avatar
Nicolas Carion committed
634
    for (const auto &m : m_keyframeList) {
635
636
        all_pos.push_back(m.first);
    }
637
    update_redo_start();
638
    bool res = true;
639
    bool first = true;
Nicolas Carion's avatar
Nicolas Carion committed
640
    for (const auto &p : all_pos) {
641
642
643
644
        if (first) { // skip first point
            first = false;
            continue;
        }
645
        res = removeKeyframe(p, local_undo, local_redo, false);
646
647
648
649
650
651
        if (!res) {
            bool undone = local_undo();
            Q_ASSERT(undone);
            return false;
        }
    }
652
653
654
    update_redo_end();
    PUSH_LAMBDA(update_redo_end, local_redo);
    PUSH_LAMBDA(update_undo_end, local_undo);
655
    UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
656
657
    return true;
}
658

659
660
661
662
663
664
665
666
667
668
669
670
bool KeyframeModel::removeAllKeyframes()
{
    QWriteLocker locker(&m_lock);
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
    bool res = removeAllKeyframes(undo, redo);
    if (res) {
        PUSH_UNDO(undo, redo, i18n("Delete all keyframes"));
    }
    return res;
}

671
672
673
674
675
676
677
678
679
680
681
682
683
mlt_keyframe_type convertToMltType(KeyframeType type)
{
    switch (type) {
    case KeyframeType::Linear:
        return mlt_keyframe_linear;
    case KeyframeType::Discrete:
        return mlt_keyframe_discrete;
    case KeyframeType::Curve:
        return mlt_keyframe_smooth;
    }
    return mlt_keyframe_linear;
}

684
685
QString KeyframeModel::getAnimProperty() const
{
686
687
688
    if (m_paramType == ParamType::Roto_spline) {
        return getRotoProperty();
    }
689
690
691
692
693
    Mlt::Properties mlt_prop;
    if (auto ptr = m_model.lock()) {
        ptr->passProperties(mlt_prop);
    }
    int ix = 0;
694
    bool first = true;
695
    std::shared_ptr<Mlt::Animation> anim(nullptr);
Nicolas Carion's avatar
Nicolas Carion committed
696
    for (const auto &keyframe : m_keyframeList) {
697
        if (first) {
698
            switch (m_paramType) {
Nicolas Carion's avatar
Nicolas Carion committed
699
700
701
702
703
704
            case ParamType::AnimatedRect:
                mlt_prop.anim_set("key", keyframe.second.second.toString().toUtf8().constData(), keyframe.first.frames(pCore->getCurrentFps()));
                break;
            default:
                mlt_prop.anim_set("key", keyframe.second.second.toDouble(), keyframe.first.frames(pCore->getCurrentFps()));
                break;
705
706
707
            }
            anim.reset(mlt_prop.get_anim("key"));
            anim->key_set_type(ix, convertToMltType(keyframe.second.first));
708
            first = false;
709
710
            ix++;
            continue;
711
        }
712
        switch (m_paramType) {
Nicolas Carion's avatar
Nicolas Carion committed
713
714
715
716
717
718
        case ParamType::AnimatedRect:
            mlt_prop.anim_set("key", keyframe.second.second.toString().toUtf8().constData(), keyframe.first.frames(pCore->getCurrentFps()));
            break;
        default:
            mlt_prop.anim_set("key", keyframe.second.second.toDouble(), keyframe.first.frames(pCore->getCurrentFps()));
            break;
719
        }
720
721
        anim->key_set_type(ix, convertToMltType(keyframe.second.first));
        ix++;
722
    }
723
724
725
726
727
728
    QString ret;
    if (anim) {
        char *cut = anim->serialize_cut();
        ret = QString(cut);
        free(cut);
    }
729
    return ret;
730
}
731

732
733
734
735
QString KeyframeModel::getRotoProperty() const
{
    QJsonDocument doc;
    if (auto ptr = m_model.lock()) {
736
737
738
        int in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
        int out = in + ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
        QVariantMap map;
Nicolas Carion's avatar
Nicolas Carion committed
739
        for (const auto &keyframe : m_keyframeList) {
740
            map.insert(QString::number(keyframe.first.frames(pCore->getCurrentFps())).rightJustified(int(log10(double(out + 1))), '0'), keyframe.second.second);
741
        }
742
        doc = QJsonDocument::fromVariant(map);
743
744
745
746
    }
    return doc.toJson();
}

747
748
749
750
void KeyframeModel::parseAnimProperty(const QString &prop)
{
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
751
752
753
    disconnect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
    removeAllKeyframes(undo, redo);
    int in = 0;
754
    int out = 0;
755
    bool useOpacity = true;
756
    Mlt::Properties mlt_prop;
757
758
    if (auto ptr = m_model.lock()) {
        in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
759
760
        out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
        ptr->passProperties(mlt_prop);
761
        useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
762
763
    } else  {
        qDebug()<<"###################\n\n/// ERROR LOCKING MODEL!!! ";
764
    }
765
766
767
768
769
770
    mlt_prop.set("key", prop.toUtf8().constData());
    // This is a fake query to force the animation to be parsed
    (void)mlt_prop.anim_get_double("key", 0, out);

    Mlt::Animation anim = mlt_prop.get_animation("key");

Nicolas Carion's avatar
Nicolas Carion committed
771
    qDebug() << "Found" << anim.key_count() << ", OUT: " << out << ", animation properties: " << prop;
772
    bool useDefaultType = !prop.contains(QLatin1Char('='));
773
    for (int i = 0; i < anim.key_count(); ++i) {
774
775
        int frame;
        mlt_keyframe_type type;
776
        anim.key_get(i, frame, type);
777
        if (useDefaultType) {
Nicolas Carion's avatar
Nicolas Carion committed
778
            // TODO: use a default user defined type
779
780
            type = mlt_keyframe_linear;
        }
781
782
        QVariant value;
        switch (m_paramType) {
Nicolas Carion's avatar
Nicolas Carion committed
783
784
        case ParamType::AnimatedRect: {
            mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
785
            if (useOpacity) {
Simon Eugster's avatar
Simon Eugster committed
786
                value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(rect.o, 0, 'f'));
787
788
789
            } else {
                value = QVariant(QStringLiteral("%1 %2 %3 %4").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h));
            }
Nicolas Carion's avatar
Nicolas Carion committed
790
791
792
793
794
            break;
        }
        default:
            value = QVariant(mlt_prop.anim_get_double("key", frame));
            break;
795
        }
796
797
798
799
800
801
802
803
804
        if (i == 0 && frame > in) {
            // Always add a keyframe at start pos
            addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo);
        } else if (frame == in && hasKeyframe(GenTime(in))) {
            // First keyframe already exists, adjust its value
            updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, true);
            continue;
        }
        addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo);
805
    }
806
    connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
807
}
808

809
810
811
812
813
814
815
void KeyframeModel::resetAnimProperty(const QString &prop)
{
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };

    // Delete all existing keyframes
    disconnect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
816
    removeAllKeyframes(undo, redo);
817
818

    Mlt::Properties mlt_prop;
819
    int in = 0;
820
    bool useOpacity = true;
821
822
    if (auto ptr = m_model.lock()) {
        in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
823
        ptr->passProperties(mlt_prop);
824
825
826
        if (m_paramType == ParamType::AnimatedRect) {
            useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
        }
827
    }
828
829
830
831
    mlt_prop.set("key", prop.toUtf8().constData());
    // This is a fake query to force the animation to be parsed
    (void)mlt_prop.anim_get_int("key", 0, 0);

832
    Mlt::Animation anim = mlt_prop.get_animation("key");
833

834
835
    qDebug() << "Found" << anim.key_count() << "animation properties";
    for (int i = 0; i < anim.key_count(); ++i) {
836
837
        int frame;
        mlt_keyframe_type type;
838
        anim.key_get(i, frame, type);
839
840
841
        if (!prop.contains(QLatin1Char('='))) {
            // TODO: use a default user defined type
            type = mlt_keyframe_linear;
842
        }
843
844
845
846
        QVariant value;
        switch (m_paramType) {
        case ParamType::AnimatedRect: {
            mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
847
            if (useOpacity) {
Simon Eugster's avatar
Simon Eugster committed
848
                value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(QString::number(rect.o, 'f')));
849
850
851
            } else {
                value = QVariant(QStringLiteral("%1 %2 %3 %4").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h));
            }
852
            break;
853
        }
854
855
856
857
        default:
            value = QVariant(mlt_prop.anim_get_double("key", frame));
            break;
        }
858
859
860
861
862
863
864
865
        if (i == 0 && frame > in) {
            // Always add a keyframe at start pos
            addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo);
        } else if (frame == in && hasKeyframe(GenTime(in))) {
            // First keyframe already exists, adjust its value
            updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, false);
            continue;
        }
866
867
868
869
870
871
872
        addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo);
    }
    QString effectName;
    if (auto ptr = m_model.lock()) {
        effectName = ptr->data(m_index, Qt::DisplayRole).toString();
    } else {
        effectName = i18n("effect");
873
    }
874
    Fun update_local = [this]() {
875
        emit dataChanged(index(0), index(int(m_keyframeList.size())), {});
876
877
878
879
880
        return true;
    };
    update_local();
    PUSH_LAMBDA(update_local, undo);
    PUSH_LAMBDA(update_local, redo);
881
882
    PUSH_UNDO(undo, redo, i18n("Reset %1", effectName));
    connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
883
884
}

885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
void KeyframeModel::parseRotoProperty(const QString &prop)
{
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };

    QJsonParseError jsonError;
    QJsonDocument doc = QJsonDocument::fromJson(prop.toLatin1(), &jsonError);
    QVariant data = doc.toVariant();
    if (data.canConvert(QVariant::Map)) {
        QMap<QString, QVariant> map = data.toMap();
        QMap<QString, QVariant>::const_iterator i = map.constBegin();
        while (i != map.constEnd()) {
            addKeyframe(GenTime(i.key().toInt(), pCore->getCurrentFps()), KeyframeType::Linear, i.value(), false, undo, redo);
            ++i;
        }
    }
}

903
QVariant KeyframeModel::getInterpolatedValue(int p) const
904
905
906
907
{
    auto pos = GenTime(p, pCore->getCurrentFps());
    return getInterpolatedValue(pos);
}
908

Nicolas Carion's avatar
Nicolas Carion committed
909
QVariant KeyframeModel::updateInterpolated(const QVariant &interpValue, double val)
910
911
912
{
    QStringList vals = interpValue.toString().split(QLatin1Char(' '));
    if (!vals.isEmpty()) {
Simon Eugster's avatar
Simon Eugster committed
913
        vals[vals.size() - 1] = QString::number(val, 'f');
914
915
916
917
    }
    return vals.join(QLatin1Char(' '));
}

918
919
920
QVariant KeyframeModel::getNormalizedValue(double newVal) const
{
    if (auto ptr = m_model.lock()) {
921
922
923
924
925
926
        double min = ptr->data(m_index, AssetParameterModel::VisualMinRole).toDouble();
        double max = ptr->data(m_index, AssetParameterModel::VisualMaxRole).toDouble();
        if (qFuzzyIsNull(min) && qFuzzyIsNull(max)) {
            min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
            max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
        }
927
928
929
930
931
        double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
        double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
        int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
        double realValue;
        if (logRole == -1) {
932
            // Logarythmic scale
933
            if (newVal >= 0.5) {
934
                realValue = norm + pow(2 * (newVal - 0.5), 10.0 / 6) * (max / factor - norm);
935
936
937
938
939
940
941
942
943
944
945
            } else {
                realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
            }
        } else {
            realValue = (newVal * (max - min) + min) / factor;
        }
        return QVariant(realValue);
    }
    return QVariant();
}

946
QVariant KeyframeModel::getInterpolatedValue(const GenTime &pos) const
947
948
949
950
{
    if (m_keyframeList.count(pos) > 0) {
        return m_keyframeList.at(pos).second;
    }
951
952
953
    if (m_keyframeList.size() == 0) {
        return QVariant();
    }
954
955
956
957
    Mlt::Properties mlt_prop;
    QString animData;
    int out = 0;
    bool useOpacity = false;
958
    if (auto ptr = m_model.lock()) {
959
        ptr->passProperties(mlt_prop);
960
        ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
961
962
963
        out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
        useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
        animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
964
    }
965
    if (m_paramType == ParamType::KeyframeParam) {
966
967
968
969
970
        if (!animData.isEmpty()) {
            mlt_prop.set("key", animData.toUtf8().constData());
            // This is a fake query to force the animation to be parsed
            (void)mlt_prop.anim_get_double("key", 0, out);
            return QVariant(mlt_prop.anim_get_double("key", pos.frames(pCore->getCurrentFps())));
971
        }
972
973
974
975
976
977
978
        return QVariant();
    } else if (m_paramType == ParamType::AnimatedRect) {
        if (!animData.isEmpty()) {
            mlt_prop.set("key", animData.toUtf8().constData());
            // This is a fake query to force the animation to be parsed
            (void)mlt_prop.anim_get_double("key", 0, out);
            mlt_rect rect = mlt_prop.anim_get_rect("key", pos.frames(pCore->getCurrentFps()));
979
            QString res = QStringLiteral("%1 %2 %3 %4").arg(int(rect.x)).arg(int(rect.y)).arg(int(rect.w)).arg(int(rect.h));
980
            if (useOpacity) {
Simon Eugster's avatar
Simon Eugster committed
981
                res.append(QStringLiteral(" %1").arg(QString::number(rect.o, 'f')));
982
            }
983
            return QVariant(res);
984
        }
985
        return QVariant();
986
987
    } else if (m_paramType == ParamType::Roto_spline) {
        // interpolate
988
989
990
991
992
993
994
995
996
997
998
        auto next = m_keyframeList.upper_bound(pos);
        if (next == m_keyframeList.cbegin()) {
            return (m_keyframeList.cbegin())->second.second;
        } else if (next == m_keyframeList.cend()) {
            auto it = m_keyframeList.cend();
            --it;
            return it->second.second;
        }
        auto prev = next;
        --prev;

999
        QSize frame = pCore->getCurrentFrameSize();
1000
1001
        QList<BPoint> p1 = RotoHelper::getPoints(prev->second.second, frame);
        QList<BPoint> p2 = RotoHelper::getPoints(next->second.second, frame);
1002
1003
1004
1005
1006
        // relPos should be in [0,1]:
        // - equal to 0 on prev keyframe
        // - equal to 1 on next keyframe
        qreal relPos = 0;
        if (next->first != prev->first) {
1007
1008
            relPos = (pos.frames(pCore->getCurrentFps()) - prev->first.frames(pCore->getCurrentFps())) / qreal
                    (((next->first - prev->first).frames(pCore->getCurrentFps())));
1009
        }
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
        int count = qMin(p1.count(), p2.count());
        QList<QVariant> vlist;
        for (int i = 0; i < count; ++i) {
            BPoint bp;
            QList<QVariant> pl;
            for (int j = 0; j < 3; ++j) {
                if (p1.at(i)[j] != p2.at(i)[j]) {
                    bp[j] = QLineF(p1.at(i)[j], p2.at(i)[j]).pointAt(relPos);
                } else {
                    bp[j] = p1.at(i)[j];
                }
                pl << QVariant(QList<QVariant>() << QVariant(bp[j].x() / frame.width()) << QVariant(bp[j].y() / frame.height()));
            }
            vlist << QVariant(pl);
        }
        return vlist;
1026
1027
    }
    return QVariant();