clipmodel.cpp 27.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/***************************************************************************
 *   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 "clipmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
22
#include "bin/projectclip.h"
Nicolas Carion's avatar
Nicolas Carion committed
23
#include "bin/projectitemmodel.h"
24
#include "clipsnapmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
25
26
#include "core.h"
#include "effects/effectstack/model/effectstackmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
27
#include "logger.hpp"
Nicolas Carion's avatar
linting    
Nicolas Carion committed
28
#include "macros.hpp"
29
#include "timelinemodel.hpp"
30
#include "trackmodel.hpp"
31
#include <QDebug>
32
#include <effects/effectsrepository.hpp>
Nicolas Carion's avatar
Nicolas Carion committed
33
#include <mlt++/MltProducer.h>
Nicolas Carion's avatar
linting    
Nicolas Carion committed
34
#include <utility>
35

Nicolas Carion's avatar
Nicolas Carion committed
36
ClipModel::ClipModel(const std::shared_ptr<TimelineModel> &parent, std::shared_ptr<Mlt::Producer> prod, const QString &binClipId, int id,
37
                     PlaylistState::ClipState state, double speed)
38
    : MoveableItem<Mlt::Producer>(parent, id)
Nicolas Carion's avatar
linting    
Nicolas Carion committed
39
    , m_producer(std::move(prod))
40
    , m_effectStack(EffectStackModel::construct(m_producer, {ObjectType::TimelineClip, m_id}, parent->m_undoStack))
41
    , m_clipMarkerModel(new ClipSnapModel())
42
    , m_binClipId(binClipId)
43
    , forceThumbReload(false)
44
45
    , m_currentState(state)
    , m_speed(speed)
46
    , m_fakeTrack(-1)
47
    , m_positionOffset(0)
48
{
49
    m_producer->set("kdenlive:id", binClipId.toUtf8().constData());
50
    m_producer->set("_kdenlive_cid", m_id);
51
    std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
52
53
    m_canBeVideo = binClip->hasVideo();
    m_canBeAudio = binClip->hasAudio();
54
    m_clipType = binClip->clipType();
55
    if (binClip) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
56
        m_endlessResize = !binClip->hasLimitedDuration();
57
58
59
    } else {
        m_endlessResize = false;
    }
60
61
    QObject::connect(m_effectStack.get(), &EffectStackModel::dataChanged, [&](const QModelIndex &, const QModelIndex &, QVector<int> roles) {
        qDebug() << "// GOT CLIP STACK DATA CHANGE: " << roles;
62
63
64
        if (m_currentTrackId != -1) {
            if (auto ptr = m_parent.lock()) {
                QModelIndex ix = ptr->makeClipIndexFromID(m_id);
65
                qDebug() << "// GOT CLIP STACK DATA CHANGE DONE: " << ix << " = " << roles;
66
                ptr->dataChanged(ix, ix, roles);
67
            }
68
69
        }
    });
70
71
}

72
int ClipModel::construct(const std::shared_ptr<TimelineModel> &parent, const QString &binClipId, int id, PlaylistState::ClipState state, double speed)
73
{
74
    id = (id == -1 ? TimelineModel::getNextId() : id);
75
    std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(binClipId);
76
77
78
79
80
81

    // We refine the state according to what the clip can actually produce
    std::pair<bool, bool> videoAudio = stateToBool(state);
    videoAudio.first = videoAudio.first && binClip->hasVideo();
    videoAudio.second = videoAudio.second && binClip->hasAudio();
    state = stateFromBool(videoAudio);
82
    std::shared_ptr<Mlt::Producer> cutProducer = binClip->getTimelineProducer(-1, id, state, speed);
83
    std::shared_ptr<ClipModel> clip(new ClipModel(parent, cutProducer, binClipId, id, state, speed));
Nicolas Carion's avatar
Nicolas Carion committed
84
    TRACE_CONSTR(clip.get(), parent, binClipId, id, state, speed);
85
    clip->setClipState_lambda(state)();
86
    parent->registerClip(clip);
87
    clip->m_clipMarkerModel->setReferenceModel(binClip->getMarkerModel(), speed);
88
    return id;
89
90
}

Nicolas Carion's avatar
Nicolas Carion committed
91
int ClipModel::construct(const std::shared_ptr<TimelineModel> &parent, const QString &binClipId, const std::shared_ptr<Mlt::Producer> &producer,
92
                         PlaylistState::ClipState state)
93
{
94
95
96
97
98

    // we hand the producer to the bin clip, and in return we get a cut to a good master producer
    // We might not be able to use directly the producer that we receive as an argument, because it cannot share the same master producer with any other
    // clipModel (due to a mlt limitation, see ProjectClip doc)

99
    int id = TimelineModel::getNextId();
100
101
102
103
104
105
106
107
    std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(binClipId);

    // We refine the state according to what the clip can actually produce
    std::pair<bool, bool> videoAudio = stateToBool(state);
    videoAudio.first = videoAudio.first && binClip->hasVideo();
    videoAudio.second = videoAudio.second && binClip->hasAudio();
    state = stateFromBool(videoAudio);

108
    double speed = 1.0;
109
110
    if (QString::fromUtf8(producer->parent().get("mlt_service")) == QLatin1String("timewarp")) {
        speed = producer->parent().get_double("warp_speed");
111
    }
112
    auto result = binClip->giveMasterAndGetTimelineProducer(id, producer, state);
113
    std::shared_ptr<ClipModel> clip(new ClipModel(parent, result.first, binClipId, id, state, speed));
114
    clip->setClipState_lambda(state)();
115
    parent->registerClip(clip);
116
    clip->m_effectStack->importEffects(producer, state, result.second);
117
    clip->m_clipMarkerModel->setReferenceModel(binClip->getMarkerModel(), speed);
118
119
120
    return id;
}

121
void ClipModel::registerClipToBin(std::shared_ptr<Mlt::Producer> service, bool registerProducer)
122
{
123
    std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
124
125
126
    if (!binClip) {
        qDebug() << "Error : Bin clip for id: " << m_binClipId << " NOT AVAILABLE!!!";
    }
Nicolas Carion's avatar
Nicolas Carion committed
127
    qDebug() << "REGISTRATION " << m_id << "ptr count" << m_parent.use_count();
Nicolas Carion's avatar
Nicolas Carion committed
128
    binClip->registerService(m_parent, m_id, std::move(service), registerProducer);
129
130
131
}

void ClipModel::deregisterClipToBin()
132
{
133
    std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
134
135
    binClip->deregisterTimelineClip(m_id);
}
136

Nicolas Carion's avatar
Nicolas Carion committed
137
ClipModel::~ClipModel() = default;
138

139
bool ClipModel::requestResize(int size, bool right, Fun &undo, Fun &redo, bool logUndo)
140
{
Nicolas Carion's avatar
Nicolas Carion committed
141
    QWriteLocker locker(&m_lock);
142
143
    // qDebug() << "RESIZE CLIP" << m_id << "target size=" << size << "right=" << right << "endless=" << m_endlessResize << "length" <<
    // m_producer->get_length();
144
    if (!m_endlessResize && (size <= 0 || size > m_producer->get_length())) {
145
146
        return false;
    }
147
    int delta = getPlaytime() - size;
148
    if (delta == 0) {
149
        return true;
150
    }
151
152
153
    int in = m_producer->get_in();
    int out = m_producer->get_out();
    int old_in = in, old_out = out;
Nicolas Carion's avatar
Nicolas Carion committed
154
    // check if there is enough space on the chosen side
155
156
157
158
159
160
161
    if (!m_endlessResize) {
        if (!right && in + delta < 0) {
            return false;
        }
        if (right && (out - delta >= m_producer->get_length())) {
            return false;
        }
162
163
164
165
166
167
    }
    if (right) {
        out -= delta;
    } else {
        in += delta;
    }
Nicolas Carion's avatar
Nicolas Carion committed
168
    // qDebug() << "Resize facts delta =" << delta << "old in" << old_in << "old_out" << old_out << "in" << in << "out" << out;
Nicolas Carion's avatar
Nicolas Carion committed
169
170
    std::function<bool(void)> track_operation = []() { return true; };
    std::function<bool(void)> track_reverse = []() { return true; };
171
172
    int outPoint = out;
    int inPoint = in;
173
    int offset = 0;
174
    if (m_endlessResize) {
175
        offset = inPoint;
176
177
178
        outPoint = out - in;
        inPoint = 0;
    }
179
180
    if (m_currentTrackId != -1) {
        if (auto ptr = m_parent.lock()) {
181
182
183
            if (ptr->getTrackById(m_currentTrackId)->isLocked()) {
                return false;
            }
184
            track_operation = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, inPoint, outPoint, right);
185
186
187
188
        } else {
            qDebug() << "Error : Moving clip failed because parent timeline is not available anymore";
            Q_ASSERT(false);
        }
189
190
191
192
193
    } else {
        // Ensure producer is long enough
        if (m_endlessResize && outPoint > m_producer->parent().get_length()) {
            m_producer->set("length", outPoint + 1);
        }
194
    }
195
196
197
198
199
200
201
202
203
    QVector<int> roles{TimelineModel::DurationRole};
    if (!right) {
        roles.push_back(TimelineModel::StartRole);
        roles.push_back(TimelineModel::InPointRole);
    } else {
        roles.push_back(TimelineModel::OutPointRole);
    }
    
    Fun operation = [this, inPoint, outPoint, roles, track_operation]() {
204
        if (track_operation()) {
205
            setInOut(inPoint, outPoint);
206
207
208
            if (m_currentTrackId > -1) {
                if (auto ptr = m_parent.lock()) {
                    QModelIndex ix = ptr->makeClipIndexFromID(m_id);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
209
                    ptr->notifyChange(ix, ix, roles);
210
211
                }
            }
212
213
214
            return true;
        }
        return false;
215
    };
216
    if (operation()) {
217
218
219
220
221
222
223
        Fun reverse = []() { return true; };
        if (logUndo) {
            // Now, we are in the state in which the timeline should be when we try to revert current action. So we can build the reverse action from here
            if (m_currentTrackId != -1) {
                if (auto ptr = m_parent.lock()) {
                    track_reverse = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, old_in, old_out, right);
                }
224
            }
225
226
227
228
229
230
            reverse = [this, old_in, old_out, track_reverse, roles]() {
                if (track_reverse()) {
                    setInOut(old_in, old_out);
                    if (m_currentTrackId > -1) {
                        if (auto ptr = m_parent.lock()) {
                            QModelIndex ix = ptr->makeClipIndexFromID(m_id);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
231
                            ptr->notifyChange(ix, ix, roles);
232
                        }
233
                    }
234
                    return true;
235
                }
236
237
238
                return false;
            };
            qDebug() << "----------\n-----------\n// ADJUSTING EFFECT LENGTH, LOGUNDO " << logUndo << ", " << old_in << "/" << inPoint << ", "
239
                 << m_producer->get_playtime();
240

241
            adjustEffectLength(right, old_in, inPoint, old_out - old_in, m_producer->get_playtime(), offset, reverse, operation, logUndo);
Nicolas Carion's avatar
Nicolas Carion committed
242
        }
243
        UPDATE_UNDO_REDO(operation, reverse, undo, redo);
244
        return true;
245
246
    }
    return false;
247
248
}

249
250
const QString ClipModel::getProperty(const QString &name) const
{
Nicolas Carion's avatar
Nicolas Carion committed
251
    READ_LOCK();
252
253
254
255
256
257
    if (service()->parent().is_valid()) {
        return QString::fromUtf8(service()->parent().get(name.toUtf8().constData()));
    }
    return QString::fromUtf8(service()->get(name.toUtf8().constData()));
}

258
259
int ClipModel::getIntProperty(const QString &name) const
{
Nicolas Carion's avatar
Nicolas Carion committed
260
    READ_LOCK();
261
262
263
264
265
266
    if (service()->parent().is_valid()) {
        return service()->parent().get_int(name.toUtf8().constData());
    }
    return service()->get_int(name.toUtf8().constData());
}

267
268
269
270
271
272
QSize ClipModel::getFrameSize() const
{
    READ_LOCK();
    if (service()->parent().is_valid()) {
        return QSize(service()->parent().get_int("meta.media.width"), service()->parent().get_int("meta.media.height"));
    }
Nicolas Carion's avatar
Nicolas Carion committed
273
    return {service()->get_int("meta.media.width"), service()->get_int("meta.media.height")};
274
275
}

276
277
278
279
280
281
282
283
284
double ClipModel::getDoubleProperty(const QString &name) const
{
    READ_LOCK();
    if (service()->parent().is_valid()) {
        return service()->parent().get_double(name.toUtf8().constData());
    }
    return service()->get_double(name.toUtf8().constData());
}

Nicolas Carion's avatar
Nicolas Carion committed
285
Mlt::Producer *ClipModel::service() const
286
{
Nicolas Carion's avatar
Nicolas Carion committed
287
    READ_LOCK();
288
    return m_producer.get();
289
290
}

291
292
293
294
295
296
std::shared_ptr<Mlt::Producer> ClipModel::getProducer()
{
    READ_LOCK();
    return m_producer;
}

297
int ClipModel::getPlaytime() const
298
{
Nicolas Carion's avatar
Nicolas Carion committed
299
    READ_LOCK();
300
    return m_producer->get_playtime();
301
}
302
303
304

void ClipModel::setTimelineEffectsEnabled(bool enabled)
{
Nicolas Carion's avatar
Nicolas Carion committed
305
    QWriteLocker locker(&m_lock);
306
    m_effectStack->setEffectStackEnabled(enabled);
307
}
308

309
310
bool ClipModel::addEffect(const QString &effectId)
{
Nicolas Carion's avatar
Nicolas Carion committed
311
    QWriteLocker locker(&m_lock);
312
313
314
315
316
317
318
    if (EffectsRepository::get()->getType(effectId) == EffectType::Audio) {
        if (m_currentState == PlaylistState::VideoOnly) {
            return false;
        }
    } else if (m_currentState == PlaylistState::AudioOnly) {
        return false;
    }
319
    m_effectStack->appendEffect(effectId);
320
321
322
    return true;
}

Nicolas Carion's avatar
Nicolas Carion committed
323
bool ClipModel::copyEffect(const std::shared_ptr<EffectStackModel> &stackModel, int rowId)
324
{
Nicolas Carion's avatar
Nicolas Carion committed
325
    QWriteLocker locker(&m_lock);
326
327
    QDomDocument doc;
    m_effectStack->copyXmlEffect(stackModel->rowToXml(rowId, doc));
328
329
330
    return true;
}

331
332
bool ClipModel::importEffects(std::shared_ptr<EffectStackModel> stackModel)
{
Nicolas Carion's avatar
Nicolas Carion committed
333
    QWriteLocker locker(&m_lock);
Nicolas Carion's avatar
Nicolas Carion committed
334
    m_effectStack->importEffects(std::move(stackModel), m_currentState);
335
336
337
    return true;
}

338
339
340
bool ClipModel::importEffects(std::weak_ptr<Mlt::Service> service)
{
    QWriteLocker locker(&m_lock);
Nicolas Carion's avatar
Nicolas Carion committed
341
    m_effectStack->importEffects(std::move(service), m_currentState);
342
343
344
    return true;
}

345
bool ClipModel::removeFade(bool fromStart)
346
{
Nicolas Carion's avatar
Nicolas Carion committed
347
    QWriteLocker locker(&m_lock);
348
    m_effectStack->removeFade(fromStart);
349
350
351
    return true;
}

352
bool ClipModel::adjustEffectLength(bool adjustFromEnd, int oldIn, int newIn, int oldDuration, int duration, int offset, Fun &undo, Fun &redo, bool logUndo)
353
{
Nicolas Carion's avatar
Nicolas Carion committed
354
    QWriteLocker locker(&m_lock);
355
    return m_effectStack->adjustStackLength(adjustFromEnd, oldIn, oldDuration, newIn, duration, offset, undo, redo, logUndo);
356
357
}

358
bool ClipModel::adjustEffectLength(const QString &effectName, int duration, int originalDuration, Fun &undo, Fun &redo)
359
{
Nicolas Carion's avatar
Nicolas Carion committed
360
    QWriteLocker locker(&m_lock);
361
    qDebug() << ".... ADJUSTING FADE LENGTH: " << duration << " / " << effectName;
362
    Fun operation = [this, duration, effectName, originalDuration]() {
363
        return m_effectStack->adjustFadeLength(duration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"), audioEnabled(),
364
                                               !isAudioOnly(), originalDuration > 0);
365
366
367
    };
    if (operation() && originalDuration > 0) {
        Fun reverse = [this, originalDuration, effectName]() {
368
            return m_effectStack->adjustFadeLength(originalDuration, effectName == QLatin1String("fadein") || effectName == QLatin1String("fade_to_black"),
369
                                                   audioEnabled(), !isAudioOnly(), true);
370
        };
Nicolas Carion's avatar
Nicolas Carion committed
371
        UPDATE_UNDO_REDO(operation, reverse, undo, redo);
372
    }
373
374
375
    return true;
}

376
bool ClipModel::audioEnabled() const
377
{
Nicolas Carion's avatar
Nicolas Carion committed
378
    READ_LOCK();
379
    return stateToBool(m_currentState).second;
380
}
381
382
383

bool ClipModel::isAudioOnly() const
{
Nicolas Carion's avatar
Nicolas Carion committed
384
    READ_LOCK();
385
    return m_currentState == PlaylistState::AudioOnly;
386
387
}

388
void ClipModel::refreshProducerFromBin(PlaylistState::ClipState state, double speed)
389
{
390
391
    // We require that the producer is not in the track when we refresh the producer, because otherwise the modification will not be propagated. Remove the clip
    // first, refresh, and then replant.
Nicolas Carion's avatar
Nicolas Carion committed
392
    QWriteLocker locker(&m_lock);
393
394
    int in = getIn();
    int out = getOut();
395
396
397
398
399
400
401
402
403
    bool revertSpeed = false;
    if (speed < 0) {
        if (m_speed > 0) {
            revertSpeed = true;
        }
    } else if (m_speed < 0) {
        revertSpeed = true;
    }

404
    if (!qFuzzyCompare(speed, m_speed) && !qFuzzyCompare(speed, 0.)) {
405
        in = in * std::abs(m_speed / speed);
406
        out = in + getPlaytime() - 1;
407
        // prevent going out of the clip's range
408
        out = std::min(out, int(double(m_producer->get_length()) * std::abs(m_speed / speed)) - 1);
409
410
411
        m_speed = speed;
        qDebug() << "changing speed" << in << out << m_speed;
    }
412
413
414
415
416
    if (revertSpeed) {
        int duration = out - in;
        in = m_producer->get_length() * std::fabs(m_speed) - out;
        out = in + duration;
    }
417
    std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
Nicolas Carion's avatar
Nicolas Carion committed
418
    std::shared_ptr<Mlt::Producer> binProducer = binClip->getTimelineProducer(m_currentTrackId, m_id, state, m_speed);
419
420
    m_producer = std::move(binProducer);
    m_producer->set_in_and_out(in, out);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
421
422
    // replant effect stack in updated service
    m_effectStack->resetService(m_producer);
423
    m_producer->set("kdenlive:id", binClip->clipId().toUtf8().constData());
424
    m_producer->set("_kdenlive_cid", m_id);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
425
    m_endlessResize = !binClip->hasLimitedDuration();
426
427
}

428
429
430
431
void ClipModel::refreshProducerFromBin()
{
    refreshProducerFromBin(m_currentState);
}
432

433
bool ClipModel::useTimewarpProducer(double speed, bool changeDuration, Fun &undo, Fun &redo)
434
{
435
    if (m_endlessResize) {
Nicolas Carion's avatar
Nicolas Carion committed
436
437
438
        // no timewarp for endless producers
        return false;
    }
439
440
441
442
    if (qFuzzyCompare(speed, m_speed)) {
        // nothing to do
        return true;
    }
Nicolas Carion's avatar
Nicolas Carion committed
443
444
    std::function<bool(void)> local_undo = []() { return true; };
    std::function<bool(void)> local_redo = []() { return true; };
445
    double previousSpeed = getSpeed();
446
    int oldDuration = getPlaytime();
447
    int newDuration = int(double(oldDuration) * std::abs(previousSpeed / speed) + 0.5);
448
449
    int oldOut = getOut();
    int oldIn = getIn();
450
    auto operation = useTimewarpProducer_lambda(speed);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
451
    auto reverse = useTimewarpProducer_lambda(previousSpeed);
452
    if (changeDuration && oldOut >= newDuration) {
453
454
455
456
457
458
459
460
461
        // in that case, we are going to shrink the clip when changing the producer. We must undo that when reloading the old producer
        reverse = [reverse, oldIn, oldOut, this]() {
            bool res = reverse();
            if (res) {
                setInOut(oldIn, oldOut);
            }
            return res;
        };
    }
462
463
    if (operation()) {
        UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo);
464
        // When calculating duration, result can be a few frames longer than possible duration so adjust
465
466
467
468
469
470
        if (changeDuration) {
            bool res = requestResize(qMin(newDuration, getMaxDuration()), true, local_undo, local_redo, true);
            if (!res) {
                local_undo();
                return false;
            }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
471
        }
472
        adjustEffectLength(false, oldIn, getIn(), oldOut - oldIn, m_producer->get_playtime(), 0, local_undo, local_redo, true);
473
474
        UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
        return true;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
475
    }
476
    qDebug() << "tw: operation fail";
477
    return false;
478
479
}

Nicolas Carion's avatar
Nicolas Carion committed
480
Fun ClipModel::useTimewarpProducer_lambda(double speed)
481
482
{
    QWriteLocker locker(&m_lock);
Nicolas Carion's avatar
Nicolas Carion committed
483
    return [speed, this]() {
484
485
        qDebug() << "timeWarp producer" << speed;
        refreshProducerFromBin(m_currentState, speed);
486
487
488
489
        if (auto ptr = m_parent.lock()) {
            QModelIndex ix = ptr->makeClipIndexFromID(m_id);
            ptr->notifyChange(ix, ix, TimelineModel::SpeedRole);
        }
490
491
        return true;
    };
492
493
}

494
QVariant ClipModel::getAudioWaveform()
495
{
Nicolas Carion's avatar
Nicolas Carion committed
496
    READ_LOCK();
497
    std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
498
499
500
501
    if (binClip) {
        return QVariant::fromValue(binClip->audioFrameCache);
    }
    return QVariant();
502
503
}

504
const QString &ClipModel::binId() const
505
{
506
    return m_binClipId;
507
}
508
509
510

std::shared_ptr<MarkerListModel> ClipModel::getMarkerModel() const
{
Nicolas Carion's avatar
Nicolas Carion committed
511
    READ_LOCK();
512
    return pCore->projectItemModel()->getClipByBinID(m_binClipId)->getMarkerModel();
513
}
514

515
516
517
518
519
520
int ClipModel::audioChannels() const
{
    READ_LOCK();
    return pCore->projectItemModel()->getClipByBinID(m_binClipId)->audioChannels();
}

521
522
int ClipModel::fadeIn() const
{
523
    return m_effectStack->getFadePosition(true);
524
525
526
527
}

int ClipModel::fadeOut() const
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
528
    return m_effectStack->getFadePosition(false);
529
530
}

531
532
double ClipModel::getSpeed() const
{
Nicolas Carion's avatar
Nicolas Carion committed
533
    return m_speed;
534
}
535
536
537
538
539
540
541
542
543

KeyframeModel *ClipModel::getKeyframeModel()
{
    return m_effectStack->getEffectKeyframeModel();
}

bool ClipModel::showKeyframes() const
{
    READ_LOCK();
544
    return !service()->get_int("kdenlive:hide_keyframes");
545
546
547
548
549
}

void ClipModel::setShowKeyframes(bool show)
{
    QWriteLocker locker(&m_lock);
550
    service()->set("kdenlive:hide_keyframes", (int)!show);
551
}
552

553
554
555
556
557
558
559
560
561
562
563
564
void ClipModel::setPosition(int pos)
{
    MoveableItem::setPosition(pos);
    m_clipMarkerModel->updateSnapModelPos(pos);
}

void ClipModel::setInOut(int in, int out)
{
    MoveableItem::setInOut(in, out);
    m_clipMarkerModel->updateSnapModelInOut(std::pair<int, int>(in, out));
}

565
566
567
568
569
void ClipModel::setCurrentTrackId(int tid, bool finalMove)
{
    if (tid == m_currentTrackId) {
        return;
    }
570
    bool registerSnap = m_currentTrackId == -1 && tid > -1;
571

572
573
574
575
    if (m_currentTrackId > -1 && tid == -1) {
        // Removing clip
        m_clipMarkerModel->deregisterSnapModel();
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
576
    MoveableItem::setCurrentTrackId(tid, finalMove);
577
578
    if (registerSnap) {
        if (auto ptr = m_parent.lock()) {
579
            m_clipMarkerModel->registerSnapModel(ptr->m_snaps, getPosition(), getIn(), getOut(), m_speed);
580
581
582
        }
    }

583
    if (finalMove && tid != -1 && m_lastTrackId != m_currentTrackId) {
584
        refreshProducerFromBin(m_currentState);
585
        m_lastTrackId = m_currentTrackId;
586
587
588
    }
}

589
Fun ClipModel::setClipState_lambda(PlaylistState::ClipState state)
590
{
Nicolas Carion's avatar
Nicolas Carion committed
591
    QWriteLocker locker(&m_lock);
592
593
594
    return [this, state]() {
        if (auto ptr = m_parent.lock()) {
            m_currentState = state;
595
596
            // Enforce producer reload
            m_lastTrackId = -1;
597
            if (m_currentTrackId != -1 && ptr->isClip(m_id)) { // if this is false, the clip is being created. Don't update model in that case
598
                refreshProducerFromBin(m_currentState);
599
600
601
                QModelIndex ix = ptr->makeClipIndexFromID(m_id);
                ptr->dataChanged(ix, ix, {TimelineModel::StatusRole});
            }
602
603
604
605
606
607
            return true;
        }
        return false;
    };
}

608
bool ClipModel::setClipState(PlaylistState::ClipState state, Fun &undo, Fun &redo)
609
610
611
612
613
614
615
{
    if (state == PlaylistState::VideoOnly && !canBeVideo()) {
        return false;
    }
    if (state == PlaylistState::AudioOnly && !canBeAudio()) {
        return false;
    }
616
617
618
    if (state == m_currentState) {
        return true;
    }
619
620
621
622
623
624
625
626
    auto old_state = m_currentState;
    auto operation = setClipState_lambda(state);
    if (operation()) {
        auto reverse = setClipState_lambda(old_state);
        UPDATE_UNDO_REDO(operation, reverse, undo, redo);
        return true;
    }
    return false;
627
628
}

629
PlaylistState::ClipState ClipModel::clipState() const
630
{
Nicolas Carion's avatar
Nicolas Carion committed
631
    READ_LOCK();
632
    return m_currentState;
633
634
635
636
637
638
}

ClipType::ProducerType ClipModel::clipType() const
{
    READ_LOCK();
    return m_clipType;
639
}
640

Nicolas Carion's avatar
Nicolas Carion committed
641
void ClipModel::passTimelineProperties(const std::shared_ptr<ClipModel> &other)
642
{
Nicolas Carion's avatar
Nicolas Carion committed
643
    READ_LOCK();
644
645
646
647
    Mlt::Properties source(m_producer->get_properties());
    Mlt::Properties dest(other->service()->get_properties());
    dest.pass_list(source, "kdenlive:hide_keyframes,kdenlive:activeeffect");
}
648
649
650
651
652

bool ClipModel::canBeVideo() const
{
    return m_canBeVideo;
}
653

654
655
656
657
bool ClipModel::canBeAudio() const
{
    return m_canBeAudio;
}
658
659
660
661
662
663

const QString ClipModel::effectNames() const
{
    READ_LOCK();
    return m_effectStack->effectNames();
}
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683

int ClipModel::getFakeTrackId() const
{
    return m_fakeTrack;
}

void ClipModel::setFakeTrackId(int fid)
{
    m_fakeTrack = fid;
}

int ClipModel::getFakePosition() const
{
    return m_fakePosition;
}

void ClipModel::setFakePosition(int fid)
{
    m_fakePosition = fid;
}
684
685
686
687
688
689
690
691
692

QDomElement ClipModel::toXml(QDomDocument &document)
{
    QDomElement container = document.createElement(QStringLiteral("clip"));
    container.setAttribute(QStringLiteral("binid"), m_binClipId);
    container.setAttribute(QStringLiteral("id"), m_id);
    container.setAttribute(QStringLiteral("in"), getIn());
    container.setAttribute(QStringLiteral("out"), getOut());
    container.setAttribute(QStringLiteral("position"), getPosition());
693
    container.setAttribute(QStringLiteral("state"), (int)m_currentState);
694
    if (auto ptr = m_parent.lock()) {
695
        int trackId = ptr->getTrackPosition(m_currentTrackId);
696
        container.setAttribute(QStringLiteral("track"), trackId);
697
698
        if (ptr->isAudioTrack(getCurrentTrackId())) {
            container.setAttribute(QStringLiteral("audioTrack"), 1);
699
700
701
702
703
704
705
706
707
            int partner = ptr->getClipSplitPartner(m_id);
            if (partner != -1) {
                int mirrorId = ptr->getMirrorVideoTrackId(m_currentTrackId);
                if (mirrorId > -1) {
                    mirrorId = ptr->getTrackPosition(mirrorId);
                }
                container.setAttribute(QStringLiteral("mirrorTrack"), mirrorId);
            } else {
                container.setAttribute(QStringLiteral("mirrorTrack"), QStringLiteral("-1"));
708
            }
709
        }
710
    }
711
    container.setAttribute(QStringLiteral("speed"), m_speed);
712
713
714
    container.appendChild(m_effectStack->toXml(document));
    return container;
}
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743

bool ClipModel::checkConsistency()
{
    if (!m_effectStack->checkConsistency()) {
        qDebug() << "Consistency check failed for effecstack";
        return false;
    }
    std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
    auto instances = binClip->timelineInstances();
    bool found = false;
    for (const auto &i : instances) {
        if (i == m_id) {
            found = true;
            break;
        }
    }
    if (!found) {
        qDebug() << "ERROR: binClip doesn't acknowledge timeline clip existence";
        return false;
    }

    if (m_currentState == PlaylistState::VideoOnly && !m_canBeVideo) {
        qDebug() << "ERROR: clip is in video state but doesn't have video";
        return false;
    }
    if (m_currentState == PlaylistState::AudioOnly && !m_canBeAudio) {
        qDebug() << "ERROR: clip is in video state but doesn't have video";
        return false;
    }
Nicolas Carion's avatar
Nicolas Carion committed
744
745
    // TODO: check speed

746
747
    return true;
}
Nicolas Carion's avatar
Nicolas Carion committed
748
749
750
751
752

int ClipModel::getSubPlaylistIndex() const
{
    return m_subPlaylistIndex;
}
753

Nicolas Carion's avatar
Nicolas Carion committed
754
755
756
757
void ClipModel::setSubPlaylistIndex(int index)
{
    m_subPlaylistIndex = index;
}
758

759
760
761
762
763
764
765
766
767
void ClipModel::setOffset(int offset)
{
    m_positionOffset = offset;
    if (auto ptr = m_parent.lock()) {
        QModelIndex ix = ptr->makeClipIndexFromID(m_id);
        ptr->dataChanged(ix, ix, {TimelineModel::PositionOffsetRole});
    }
}

768
769
770
void ClipModel::setGrab(bool grab)
{
    QWriteLocker locker(&m_lock);
771
772
773
    if (grab == m_grabbed) {
        return;
    }
774
775
776
777
778
779
780
    m_grabbed = grab;
    if (auto ptr = m_parent.lock()) {
        QModelIndex ix = ptr->makeClipIndexFromID(m_id);
        ptr->dataChanged(ix, ix, {TimelineModel::GrabbedRole});
    }
}

781
782
783
784
785
786
787
788
void ClipModel::setSelected(bool sel)
{
    QWriteLocker locker(&m_lock);
    if (sel == selected) {
        return;
    }
    selected = sel;
    if (auto ptr = m_parent.lock()) {
789
790
791
792
        if (m_currentTrackId != -1) {
            QModelIndex ix = ptr->makeClipIndexFromID(m_id);
            ptr->dataChanged(ix, ix, {TimelineModel::SelectedRole});
        }
793
794
795
    }
}

796
797
798
799
800
801
802
803
804
805
806
void ClipModel::clearOffset()
{
    if (m_positionOffset != 0) {
        setOffset(0);
    }
}

int ClipModel::getOffset() const
{
    return m_positionOffset;
}
807
808
809
810
811
812
813
814
815

int ClipModel::getMaxDuration() const
{
    READ_LOCK();
    if (m_endlessResize) {
        return -1;
    }
    return m_producer->get_length();
}
816