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();
}