clipmodel.cpp 26.7 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
    if (!right && in + delta < 0 && !m_endlessResize) {
156
        return false;
Nicolas Carion's avatar
Nicolas Carion committed
157
    }
158
    if (!m_endlessResize && right && (out - delta >= m_producer->get_length())) {
159
160
161
162
163
164
165
        return false;
    }
    if (right) {
        out -= delta;
    } else {
        in += delta;
    }
Nicolas Carion's avatar
Nicolas Carion committed
166
    // qDebug() << "Resize facts delta =" << delta << "old in" << old_in << "old_out" << old_out << "in" << in << "out" << out;
Nicolas Carion's avatar
Nicolas Carion committed
167
168
    std::function<bool(void)> track_operation = []() { return true; };
    std::function<bool(void)> track_reverse = []() { return true; };
169
170
    int outPoint = out;
    int inPoint = in;
171
    int offset = 0;
172
    if (m_endlessResize) {
173
        offset = inPoint;
174
175
176
        outPoint = out - in;
        inPoint = 0;
    }
177
178
    if (m_currentTrackId != -1) {
        if (auto ptr = m_parent.lock()) {
179
180
181
            if (ptr->getTrackById(m_currentTrackId)->isLocked()) {
                return false;
            }
182
            track_operation = ptr->getTrackById(m_currentTrackId)->requestClipResize_lambda(m_id, inPoint, outPoint, right);
183
184
185
186
        } else {
            qDebug() << "Error : Moving clip failed because parent timeline is not available anymore";
            Q_ASSERT(false);
        }
187
188
189
190
191
    } else {
        // Ensure producer is long enough
        if (m_endlessResize && outPoint > m_producer->parent().get_length()) {
            m_producer->set("length", outPoint + 1);
        }
192
    }
193
194
195
196
197
198
199
200
201
    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]() {
202
        if (track_operation()) {
203
            setInOut(inPoint, outPoint);
204
205
206
            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
207
                    ptr->notifyChange(ix, ix, roles);
208
209
                }
            }
210
211
212
            return true;
        }
        return false;
213
    };
214
    if (operation()) {
215
        // 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
216
217
218
        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);
219
220
            }
        }
221
        Fun reverse = [this, old_in, old_out, track_reverse, roles]() {
222
            if (track_reverse()) {
223
                setInOut(old_in, old_out);
224
225
226
                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
227
                        ptr->notifyChange(ix, ix, roles);
228
                    }
229
                }
230
231
232
233
                return true;
            }
            return false;
        };
234
235
        qDebug() << "----------\n-----------\n// ADJUSTING EFFECT LENGTH, LOGUNDO " << logUndo << ", " << old_in << "/" << inPoint << ", "
                 << m_producer->get_playtime();
Nicolas Carion's avatar
Nicolas Carion committed
236
        if (logUndo) {
237
            adjustEffectLength(right, old_in, inPoint, old_out - old_in, m_producer->get_playtime(), offset, reverse, operation, logUndo);
Nicolas Carion's avatar
Nicolas Carion committed
238
        }
239
        UPDATE_UNDO_REDO(operation, reverse, undo, redo);
240
        return true;
241
242
    }
    return false;
243
244
}

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

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

263
264
265
266
267
268
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
269
    return {service()->get_int("meta.media.width"), service()->get_int("meta.media.height")};
270
271
}

272
273
274
275
276
277
278
279
280
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
281
Mlt::Producer *ClipModel::service() const
282
{
Nicolas Carion's avatar
Nicolas Carion committed
283
    READ_LOCK();
284
    return m_producer.get();
285
286
}

287
288
289
290
291
292
std::shared_ptr<Mlt::Producer> ClipModel::getProducer()
{
    READ_LOCK();
    return m_producer;
}

293
int ClipModel::getPlaytime() const
294
{
Nicolas Carion's avatar
Nicolas Carion committed
295
    READ_LOCK();
296
    return m_producer->get_playtime();
297
}
298
299
300

void ClipModel::setTimelineEffectsEnabled(bool enabled)
{
Nicolas Carion's avatar
Nicolas Carion committed
301
    QWriteLocker locker(&m_lock);
302
    m_effectStack->setEffectStackEnabled(enabled);
303
}
304

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

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

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

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

341
bool ClipModel::removeFade(bool fromStart)
342
{
Nicolas Carion's avatar
Nicolas Carion committed
343
    QWriteLocker locker(&m_lock);
344
    m_effectStack->removeFade(fromStart);
345
346
347
    return true;
}

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

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

372
bool ClipModel::audioEnabled() const
373
{
Nicolas Carion's avatar
Nicolas Carion committed
374
    READ_LOCK();
375
    return stateToBool(m_currentState).second;
376
}
377
378
379

bool ClipModel::isAudioOnly() const
{
Nicolas Carion's avatar
Nicolas Carion committed
380
    READ_LOCK();
381
    return m_currentState == PlaylistState::AudioOnly;
382
383
}

384
void ClipModel::refreshProducerFromBin(PlaylistState::ClipState state, double speed)
385
{
386
387
    // 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
388
    QWriteLocker locker(&m_lock);
389
390
    int in = getIn();
    int out = getOut();
391
    if (!qFuzzyCompare(speed, m_speed) && !qFuzzyCompare(speed, 0.)) {
392
        in = in * std::abs(m_speed / speed);
393
        out = in + getPlaytime() - 1;
394
        // prevent going out of the clip's range
395
        out = std::min(out, int(double(m_producer->get_length()) * std::abs(m_speed / speed)) - 1);
396
397
398
        m_speed = speed;
        qDebug() << "changing speed" << in << out << m_speed;
    }
399
    std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
Nicolas Carion's avatar
Nicolas Carion committed
400
    std::shared_ptr<Mlt::Producer> binProducer = binClip->getTimelineProducer(m_currentTrackId, m_id, state, m_speed);
401
402
    m_producer = std::move(binProducer);
    m_producer->set_in_and_out(in, out);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
403
404
    // replant effect stack in updated service
    m_effectStack->resetService(m_producer);
405
    m_producer->set("kdenlive:id", binClip->clipId().toUtf8().constData());
406
    m_producer->set("_kdenlive_cid", m_id);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
407
    m_endlessResize = !binClip->hasLimitedDuration();
408
409
}

410
411
412
413
void ClipModel::refreshProducerFromBin()
{
    refreshProducerFromBin(m_currentState);
}
414

415
bool ClipModel::useTimewarpProducer(double speed, Fun &undo, Fun &redo)
416
{
417
    if (m_endlessResize) {
Nicolas Carion's avatar
Nicolas Carion committed
418
419
420
        // no timewarp for endless producers
        return false;
    }
421
422
423
424
    if (qFuzzyCompare(speed, m_speed)) {
        // nothing to do
        return true;
    }
Nicolas Carion's avatar
Nicolas Carion committed
425
426
    std::function<bool(void)> local_undo = []() { return true; };
    std::function<bool(void)> local_redo = []() { return true; };
427
    double previousSpeed = getSpeed();
428
    int oldDuration = getPlaytime();
429
    int newDuration = int(double(oldDuration) * std::abs(previousSpeed / speed) + 0.5);
430
431
    int oldOut = getOut();
    int oldIn = getIn();
432
    auto operation = useTimewarpProducer_lambda(speed);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
433
    auto reverse = useTimewarpProducer_lambda(previousSpeed);
434
435
436
437
438
439
440
441
442
443
    if (oldOut >= newDuration) {
        // 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;
        };
    }
444
445
    if (operation()) {
        UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo);
446
447
        // When calculating duration, result can be a few frames longer than possible duration so adjust
        bool res = requestResize(qMin(newDuration, getMaxDuration()), true, local_undo, local_redo, true);
448
449
450
        if (!res) {
            local_undo();
            return false;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
451
        }
452
        adjustEffectLength(false, oldIn, getIn(), oldOut - oldIn, m_producer->get_playtime(), 0, local_undo, local_redo, true);
453
454
        UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
        return true;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
455
    }
456
    qDebug() << "tw: operation fail";
457
    return false;
458
459
}

Nicolas Carion's avatar
Nicolas Carion committed
460
Fun ClipModel::useTimewarpProducer_lambda(double speed)
461
462
{
    QWriteLocker locker(&m_lock);
Nicolas Carion's avatar
Nicolas Carion committed
463
    return [speed, this]() {
464
465
        qDebug() << "timeWarp producer" << speed;
        refreshProducerFromBin(m_currentState, speed);
466
467
468
469
        if (auto ptr = m_parent.lock()) {
            QModelIndex ix = ptr->makeClipIndexFromID(m_id);
            ptr->notifyChange(ix, ix, TimelineModel::SpeedRole);
        }
470
471
        return true;
    };
472
473
}

474
QVariant ClipModel::getAudioWaveform()
475
{
Nicolas Carion's avatar
Nicolas Carion committed
476
    READ_LOCK();
477
    std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(m_binClipId);
478
479
480
481
    if (binClip) {
        return QVariant::fromValue(binClip->audioFrameCache);
    }
    return QVariant();
482
483
}

484
const QString &ClipModel::binId() const
485
{
486
    return m_binClipId;
487
}
488
489
490

std::shared_ptr<MarkerListModel> ClipModel::getMarkerModel() const
{
Nicolas Carion's avatar
Nicolas Carion committed
491
    READ_LOCK();
492
    return pCore->projectItemModel()->getClipByBinID(m_binClipId)->getMarkerModel();
493
}
494

495
496
497
498
499
500
int ClipModel::audioChannels() const
{
    READ_LOCK();
    return pCore->projectItemModel()->getClipByBinID(m_binClipId)->audioChannels();
}

501
502
int ClipModel::fadeIn() const
{
503
    return m_effectStack->getFadePosition(true);
504
505
506
507
}

int ClipModel::fadeOut() const
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
508
    return m_effectStack->getFadePosition(false);
509
510
}

511
512
double ClipModel::getSpeed() const
{
Nicolas Carion's avatar
Nicolas Carion committed
513
    return m_speed;
514
}
515
516
517
518
519
520
521
522
523

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

bool ClipModel::showKeyframes() const
{
    READ_LOCK();
524
    return !service()->get_int("kdenlive:hide_keyframes");
525
526
527
528
529
}

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

533
534
535
536
537
538
539
540
541
542
543
544
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));
}

545
546
547
548
549
void ClipModel::setCurrentTrackId(int tid, bool finalMove)
{
    if (tid == m_currentTrackId) {
        return;
    }
550
    bool registerSnap = m_currentTrackId == -1 && tid > -1;
551

552
553
554
555
    if (m_currentTrackId > -1 && tid == -1) {
        // Removing clip
        m_clipMarkerModel->deregisterSnapModel();
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
556
    MoveableItem::setCurrentTrackId(tid, finalMove);
557
558
    if (registerSnap) {
        if (auto ptr = m_parent.lock()) {
559
            m_clipMarkerModel->registerSnapModel(ptr->m_snaps, getPosition(), getIn(), getOut(), m_speed);
560
561
562
        }
    }

563
    if (finalMove && tid != -1 && m_lastTrackId != m_currentTrackId) {
564
        refreshProducerFromBin(m_currentState);
565
        m_lastTrackId = m_currentTrackId;
566
567
568
    }
}

569
Fun ClipModel::setClipState_lambda(PlaylistState::ClipState state)
570
{
Nicolas Carion's avatar
Nicolas Carion committed
571
    QWriteLocker locker(&m_lock);
572
573
574
    return [this, state]() {
        if (auto ptr = m_parent.lock()) {
            m_currentState = state;
575
576
            // Enforce producer reload
            m_lastTrackId = -1;
577
            if (m_currentTrackId != -1 && ptr->isClip(m_id)) { // if this is false, the clip is being created. Don't update model in that case
578
                refreshProducerFromBin(m_currentState);
579
580
581
                QModelIndex ix = ptr->makeClipIndexFromID(m_id);
                ptr->dataChanged(ix, ix, {TimelineModel::StatusRole});
            }
582
583
584
585
586
587
            return true;
        }
        return false;
    };
}

588
bool ClipModel::setClipState(PlaylistState::ClipState state, Fun &undo, Fun &redo)
589
590
591
592
593
594
595
{
    if (state == PlaylistState::VideoOnly && !canBeVideo()) {
        return false;
    }
    if (state == PlaylistState::AudioOnly && !canBeAudio()) {
        return false;
    }
596
597
598
    if (state == m_currentState) {
        return true;
    }
599
600
601
602
603
604
605
606
    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;
607
608
}

609
PlaylistState::ClipState ClipModel::clipState() const
610
{
Nicolas Carion's avatar
Nicolas Carion committed
611
    READ_LOCK();
612
    return m_currentState;
613
614
615
616
617
618
}

ClipType::ProducerType ClipModel::clipType() const
{
    READ_LOCK();
    return m_clipType;
619
}
620

Nicolas Carion's avatar
Nicolas Carion committed
621
void ClipModel::passTimelineProperties(const std::shared_ptr<ClipModel> &other)
622
{
Nicolas Carion's avatar
Nicolas Carion committed
623
    READ_LOCK();
624
625
626
627
    Mlt::Properties source(m_producer->get_properties());
    Mlt::Properties dest(other->service()->get_properties());
    dest.pass_list(source, "kdenlive:hide_keyframes,kdenlive:activeeffect");
}
628
629
630
631
632

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

634
635
636
637
bool ClipModel::canBeAudio() const
{
    return m_canBeAudio;
}
638
639
640
641
642
643

const QString ClipModel::effectNames() const
{
    READ_LOCK();
    return m_effectStack->effectNames();
}
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663

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;
}
664
665
666
667
668
669
670
671
672

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());
673
    container.setAttribute(QStringLiteral("state"), (int)m_currentState);
674
    if (auto ptr = m_parent.lock()) {
675
        int trackId = ptr->getTrackPosition(m_currentTrackId);
676
        container.setAttribute(QStringLiteral("track"), trackId);
677
678
        if (ptr->isAudioTrack(getCurrentTrackId())) {
            container.setAttribute(QStringLiteral("audioTrack"), 1);
679
680
681
682
683
684
685
686
687
            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"));
688
            }
689
        }
690
    }
691
    container.setAttribute(QStringLiteral("speed"), m_speed);
692
693
694
    container.appendChild(m_effectStack->toXml(document));
    return container;
}
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723

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
724
725
    // TODO: check speed

726
727
    return true;
}
Nicolas Carion's avatar
Nicolas Carion committed
728
729
730
731
732

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

Nicolas Carion's avatar
Nicolas Carion committed
734
735
736
737
void ClipModel::setSubPlaylistIndex(int index)
{
    m_subPlaylistIndex = index;
}
738

739
740
741
742
743
744
745
746
747
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});
    }
}

748
749
750
void ClipModel::setGrab(bool grab)
{
    QWriteLocker locker(&m_lock);
751
752
753
    if (grab == m_grabbed) {
        return;
    }
754
755
756
757
758
759
760
    m_grabbed = grab;
    if (auto ptr = m_parent.lock()) {
        QModelIndex ix = ptr->makeClipIndexFromID(m_id);
        ptr->dataChanged(ix, ix, {TimelineModel::GrabbedRole});
    }
}

761
762
763
764
765
766
767
768
void ClipModel::setSelected(bool sel)
{
    QWriteLocker locker(&m_lock);
    if (sel == selected) {
        return;
    }
    selected = sel;
    if (auto ptr = m_parent.lock()) {
769
770
771
772
        if (m_currentTrackId != -1) {
            QModelIndex ix = ptr->makeClipIndexFromID(m_id);
            ptr->dataChanged(ix, ix, {TimelineModel::SelectedRole});
        }
773
774
775
    }
}

776
777
778
779
780
781
782
783
784
785
786
void ClipModel::clearOffset()
{
    if (m_positionOffset != 0) {
        setOffset(0);
    }
}

int ClipModel::getOffset() const
{
    return m_positionOffset;
}
787
788
789
790
791
792
793
794
795

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