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
    clip->m_effectStack->importEffects(producer, state, result.second);
116
    parent->registerClip(clip);
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
    m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId), m_currentState);
323
324
325
    return true;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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());
672
    container.setAttribute(QStringLiteral("state"), (int)m_currentState);
673
    if (auto ptr = m_parent.lock()) {
674
        int trackId = ptr->getTrackPosition(m_currentTrackId);
675
        container.setAttribute(QStringLiteral("track"), trackId);
676
677
        if (ptr->isAudioTrack(getCurrentTrackId())) {
            container.setAttribute(QStringLiteral("audioTrack"), 1);
678
679
680
681
682
683
684
685
686
            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"));
687
            }
688
        }
689
    }
690
    container.setAttribute(QStringLiteral("speed"), m_speed);
691
692
693
    container.appendChild(m_effectStack->toXml(document));
    return container;
}
694
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

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

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

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

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

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

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

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

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

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

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