timelinemodel.cpp 202 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/***************************************************************************
 *   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 "timelinemodel.hpp"
23
#include "assets/model/assetparametermodel.hpp"
24
#include "bin/projectclip.h"
25
#include "bin/projectitemmodel.h"
26
#include "clipmodel.hpp"
27
#include "compositionmodel.hpp"
Nicolas Carion's avatar
style    
Nicolas Carion committed
28
#include "core.h"
Nicolas Carion's avatar
Nicolas Carion committed
29
#include "doc/docundostack.hpp"
30
#include "effects/effectsrepository.hpp"
31
#include "bin/model/subtitlemodel.hpp"
32
#include "effects/effectstack/model/effectstackmodel.hpp"
33
#include "jobs/jobmanager.h"
34
#include "groupsmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
35
#include "kdenlivesettings.h"
36
#include "logger.hpp"
37
#include "snapmodel.hpp"
38
#include "timelinefunctions.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
39
#include "trackmodel.hpp"
40

41
#include <QDebug>
42
#include <QThread>
43
#include <QModelIndex>
Nicolas Carion's avatar
Nicolas Carion committed
44
#include <klocalizedstring.h>
Nicolas Carion's avatar
Nicolas Carion committed
45
#include <mlt++/MltConsumer.h>
Nicolas Carion's avatar
Nicolas Carion committed
46
#include <mlt++/MltField.h>
47
#include <mlt++/MltProfile.h>
Nicolas Carion's avatar
Nicolas Carion committed
48
#include <mlt++/MltTractor.h>
49
#include <mlt++/MltTransition.h>
50
#include <queue>
51

52
53
#include "macros.hpp"

Nicolas Carion's avatar
Nicolas Carion committed
54
55
56
57
58
59
60
61
62
63
64
65
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic ignored "-Wfloat-equal"
#pragma GCC diagnostic ignored "-Wshadow"
#pragma GCC diagnostic ignored "-Wpedantic"
#include <rttr/registration>
#pragma GCC diagnostic pop
RTTR_REGISTRATION
{
    using namespace rttr;
    registration::class_<TimelineModel>("TimelineModel")
66
        .method("setTrackLockedState", &TimelineModel::setTrackLockedState)(parameter_names("trackId", "lock"))
67
68
        .method("requestClipMove", select_overload<bool(int, int, int, bool, bool, bool, bool)>(&TimelineModel::requestClipMove))(
            parameter_names("clipId", "trackId", "position", "moveMirrorTracks", "updateView", "logUndo", "invalidateTimeline"))
Nicolas Carion's avatar
Nicolas Carion committed
69
70
71
72
73
        .method("requestCompositionMove", select_overload<bool(int, int, int, bool, bool)>(&TimelineModel::requestCompositionMove))(
            parameter_names("compoId", "trackId", "position", "updateView", "logUndo"))
        .method("requestClipInsertion", select_overload<bool(const QString &, int, int, int &, bool, bool, bool)>(&TimelineModel::requestClipInsertion))(
            parameter_names("binClipId", "trackId", "position", "id", "logUndo", "refreshView", "useTargets"))
        .method("requestItemDeletion", select_overload<bool(int, bool)>(&TimelineModel::requestItemDeletion))(parameter_names("clipId", "logUndo"))
74
75
        .method("requestGroupMove", select_overload<bool(int, int, int, int, bool, bool, bool)>(&TimelineModel::requestGroupMove))(
            parameter_names("itemId", "groupId", "delta_track", "delta_pos", "moveMirrorTracks", "updateView", "logUndo"))
Nicolas Carion's avatar
Nicolas Carion committed
76
77
78
79
        .method("requestGroupDeletion", select_overload<bool(int, bool)>(&TimelineModel::requestGroupDeletion))(parameter_names("clipId", "logUndo"))
        .method("requestItemResize", select_overload<int(int, int, bool, bool, int, bool)>(&TimelineModel::requestItemResize))(
            parameter_names("itemId", "size", "right", "logUndo", "snapDistance", "allowSingleResize"))
        .method("requestClipsGroup", select_overload<int(const std::unordered_set<int> &, bool, GroupType)>(&TimelineModel::requestClipsGroup))(
80
81
82
            parameter_names("itemIds", "logUndo", "type"))
        .method("requestClipUngroup", select_overload<bool(int, bool)>(&TimelineModel::requestClipUngroup))(parameter_names("itemId", "logUndo"))
        .method("requestClipsUngroup", &TimelineModel::requestClipsUngroup)(parameter_names("itemIds", "logUndo"))
Nicolas Carion's avatar
Nicolas Carion committed
83
84
        .method("requestTrackInsertion", select_overload<bool(int, int &, const QString &, bool)>(&TimelineModel::requestTrackInsertion))(
            parameter_names("pos", "id", "trackName", "audioTrack"))
85
        .method("requestTrackDeletion", select_overload<bool(int)>(&TimelineModel::requestTrackDeletion))(parameter_names("trackId"))
86
        .method("requestClearSelection", select_overload<bool(bool)>(&TimelineModel::requestClearSelection))(parameter_names("onDeletion"))
87
88
        .method("requestAddToSelection", &TimelineModel::requestAddToSelection)(parameter_names("itemId", "clear"))
        .method("requestRemoveFromSelection", &TimelineModel::requestRemoveFromSelection)(parameter_names("itemId"))
89
90
91
        .method("requestSetSelection", select_overload<bool(const std::unordered_set<int> &)>(&TimelineModel::requestSetSelection))(parameter_names("itemIds"))
        .method("requestFakeClipMove", select_overload<bool(int, int, int, bool, bool, bool)>(&TimelineModel::requestFakeClipMove))(
            parameter_names("clipId", "trackId", "position", "updateView", "logUndo", "invalidateTimeline"))
92
93
        .method("requestFakeGroupMove", select_overload<bool(int, int, int, int, bool, bool)>(&TimelineModel::requestFakeGroupMove))(
            parameter_names("clipId", "groupId", "delta_track", "delta_pos", "updateView", "logUndo"))
94
        .method("suggestClipMove", &TimelineModel::suggestClipMove)(parameter_names("clipId", "trackId", "position", "cursorPosition", "snapDistance", "moveMirrorTracks"))
95
96
        .method("suggestCompositionMove",
                &TimelineModel::suggestCompositionMove)(parameter_names("compoId", "trackId", "position", "cursorPosition", "snapDistance"))
97
98
        // .method("addSnap", &TimelineModel::addSnap)(parameter_names("pos"))
        // .method("removeSnap", &TimelineModel::addSnap)(parameter_names("pos"))
99
100
101
        // .method("requestCompositionInsertion", select_overload<bool(const QString &, int, int, int, std::unique_ptr<Mlt::Properties>, int &, bool)>(
        //                                            &TimelineModel::requestCompositionInsertion))(
        //     parameter_names("transitionId", "trackId", "position", "length", "transProps", "id", "logUndo"))
102
        .method("requestClipTimeWarp", select_overload<bool(int, double,bool,bool)>(&TimelineModel::requestClipTimeWarp))(parameter_names("clipId", "speed","pitchCompensate","changeDuration"));
Nicolas Carion's avatar
Nicolas Carion committed
103
104
}

105
int TimelineModel::next_id = 0;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
106
int TimelineModel::seekDuration = 30000;
107

108
TimelineModel::TimelineModel(Mlt::Profile *profile, std::weak_ptr<DocUndoStack> undo_stack)
109
    : QAbstractItemModel_shared_from_this()
Vincent Pinon's avatar
Vincent Pinon committed
110
    , m_blockRefresh(false)
111
    , m_tractor(new Mlt::Tractor(*profile))
112
    , m_masterStack(nullptr)
113
    , m_snaps(new SnapModel())
Nicolas Carion's avatar
Nicolas Carion committed
114
    , m_undoStack(std::move(undo_stack))
115
    , m_profile(profile)
116
    , m_blackClip(new Mlt::Producer(*profile, "color:black"))
117
118
    , m_lock(QReadWriteLock::Recursive)
    , m_timelineEffectsEnabled(true)
Nicolas Carion's avatar
Nicolas Carion committed
119
    , m_id(getNextId())
120
    , m_overlayTrackCount(-1)
121
    , m_videoTarget(-1)
122
    , m_editMode(TimelineMode::NormalEdit)
123
    , m_closing(false)
124
{
125
126
127
    // Create black background track
    m_blackClip->set("id", "black_track");
    m_blackClip->set("mlt_type", "producer");
128
    m_blackClip->set("aspect_ratio", 1);
129
    m_blackClip->set("length", INT_MAX);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
130
    m_blackClip->set("mlt_image_format", "rgb24a");
131
    m_blackClip->set("set.test_audio", 0);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
132
    m_blackClip->set_in_and_out(0, TimelineModel::seekDuration);
133
    m_tractor->insert_track(*m_blackClip, 0);
134

135
    TRACE_CONSTR(this);
136
137
}

138
139
void TimelineModel::prepareClose()
{
140
    requestClearSelection(true);
141
    QWriteLocker locker(&m_lock);
142
    // Unlock all tracks to allow deleting clip from tracks
143
144
145
    m_closing = true;
    auto it = m_allTracks.begin();
    while (it != m_allTracks.end()) {
146
        (*it)->unlock();
147
148
        ++it;
    }
149
150
    m_subtitleModel.reset();
    //m_subtitleModel->removeAllSubtitles();
151
152
}

153
154
TimelineModel::~TimelineModel()
{
155
    std::vector<int> all_ids;
Nicolas Carion's avatar
Nicolas Carion committed
156
    for (auto tracks : m_iteratorTable) {
157
158
        all_ids.push_back(tracks.first);
    }
Nicolas Carion's avatar
Nicolas Carion committed
159
    for (auto tracks : all_ids) {
160
        deregisterTrack_lambda(tracks)();
161
    }
162
    for (const auto &clip : m_allClips) {
163
164
        clip.second->deregisterClipToBin();
    }
165
166
}

167
168
int TimelineModel::getTracksCount() const
{
169
    READ_LOCK();
170
    int count = m_tractor->count();
171
172
    if (m_overlayTrackCount > -1) {
        count -= m_overlayTrackCount;
173
    }
174
    Q_ASSERT(count >= 0);
175
    // don't count the black background track
Nicolas Carion's avatar
Nicolas Carion committed
176
    Q_ASSERT(count - 1 == static_cast<int>(m_allTracks.size()));
177
    return count - 1;
178
}
179

180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
QPair<int, int> TimelineModel::getAVtracksCount() const
{
    QPair <int, int> tracks{0, 0};
    auto it = m_allTracks.cbegin();
    while (it != m_allTracks.cend()) {
        if ((*it)->isAudioTrack()) {
            tracks.second++;
        } else {
            tracks.first++;
        }
        ++it;
    }
    if (m_overlayTrackCount > -1) {
        tracks.first -= m_overlayTrackCount;
    }
    return tracks;
}

QList<int> TimelineModel::getTracksIds(bool audio) const
{
    QList <int> trackIds;
    auto it = m_allTracks.cbegin();
    while (it != m_allTracks.cend()) {
        if ((*it)->isAudioTrack() == audio) {
            trackIds.insert(-1, (*it)->getId());
        }
        ++it;
    }
    return trackIds;
}

211
212
int TimelineModel::getTrackIndexFromPosition(int pos) const
{
213
    Q_ASSERT(pos >= 0 && pos < (int)m_allTracks.size());
214
    READ_LOCK();
215
    auto it = m_allTracks.cbegin();
216
217
218
219
220
221
222
    while (pos > 0) {
        it++;
        pos--;
    }
    return (*it)->getId();
}

223
int TimelineModel::getClipsCount() const
224
{
225
226
227
    READ_LOCK();
    int size = int(m_allClips.size());
    return size;
228
}
229

230
231
232
233
234
235
int TimelineModel::getCompositionsCount() const
{
    READ_LOCK();
    int size = int(m_allCompositions.size());
    return size;
}
236

237
int TimelineModel::getClipTrackId(int clipId) const
238
{
239
    READ_LOCK();
240
241
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
242
    return clip->getCurrentTrackId();
243
244
}

245
246
247
248
249
250
251
252
253
254
int TimelineModel::getCompositionTrackId(int compoId) const
{
    Q_ASSERT(m_allCompositions.count(compoId) > 0);
    const auto trans = m_allCompositions.at(compoId);
    return trans->getCurrentTrackId();
}

int TimelineModel::getItemTrackId(int itemId) const
{
    READ_LOCK();
255
    Q_ASSERT(isItem(itemId));
256
257
258
    if (isClip(itemId)) {
        return getClipTrackId(itemId);
    }
259
260
261
    if (isComposition(itemId)) {
        return getCompositionTrackId(itemId);
    }
262
    return -1;
263
264
}

265
int TimelineModel::getClipPosition(int clipId) const
266
{
267
    READ_LOCK();
268
269
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
270
271
    int pos = clip->getPosition();
    return pos;
272
273
}

274
275
276
277
278
279
280
double TimelineModel::getClipSpeed(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    return m_allClips.at(clipId)->getSpeed();
}

281
282
283
284
285
286
287
int TimelineModel::getClipSplitPartner(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    return m_groups->getSplitPartner(clipId);
}

288
289
290
291
292
int TimelineModel::getClipIn(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
293
294
295
296
297
298
299
300
301
    return clip->getIn();
}

PlaylistState::ClipState TimelineModel::getClipState(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
    return clip->clipState();
302
303
304
305
306
307
308
309
310
311
312
}

const QString TimelineModel::getClipBinId(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
    QString id = clip->binId();
    return id;
}

313
int TimelineModel::getClipPlaytime(int clipId) const
314
{
315
    READ_LOCK();
316
    Q_ASSERT(isClip(clipId));
317
    const auto clip = m_allClips.at(clipId);
318
319
    int playtime = clip->getPlaytime();
    return playtime;
320
321
}

322
323
324
325
326
327
328
329
QSize TimelineModel::getClipFrameSize(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(isClip(clipId));
    const auto clip = m_allClips.at(clipId);
    return clip->getFrameSize();
}

330
int TimelineModel::getTrackClipsCount(int trackId) const
331
{
332
    READ_LOCK();
333
    Q_ASSERT(isTrack(trackId));
334
    int count = getTrackById_const(trackId)->getClipsCount();
335
    return count;
336
337
}

338
339
340
341
342
343
344
int TimelineModel::getClipByStartPosition(int trackId, int position) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    return getTrackById_const(trackId)->getClipByStartPosition(position);
}

345
346
347
348
349
350
351
int TimelineModel::getClipByPosition(int trackId, int position) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    return getTrackById_const(trackId)->getClipByPosition(position);
}

352
353
354
355
356
357
358
int TimelineModel::getCompositionByPosition(int trackId, int position) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    return getTrackById_const(trackId)->getCompositionByPosition(position);
}

359
360
361
362
363
364
365
366
367
368
369
370
371
int TimelineModel::getSubtitleByPosition(int position) const
{
    READ_LOCK();
    GenTime startTime(position, pCore->getCurrentFps());
    auto findResult = std::find_if(std::begin(m_allSubtitles), std::end(m_allSubtitles), [&](const std::pair<int, GenTime> &pair) {
        return pair.second == startTime;
    });
    if (findResult != std::end(m_allSubtitles)) {
        return findResult->first;
    }
    return -1;
}

372
int TimelineModel::getTrackPosition(int trackId) const
373
{
374
    READ_LOCK();
375
    Q_ASSERT(isTrack(trackId));
376
    auto it = m_allTracks.cbegin();
377
    int pos = (int)std::distance(it, (decltype(it))m_iteratorTable.at(trackId));
378
    return pos;
379
380
}

381
382
383
384
385
386
387
int TimelineModel::getTrackMltIndex(int trackId) const
{
    READ_LOCK();
    // Because of the black track that we insert in first position, the mlt index is the position + 1
    return getTrackPosition(trackId) + 1;
}

388
int TimelineModel::getTrackSortValue(int trackId, int separated) const
389
{
390
    if (separated == 1) {
391
        // This will be A2, A1, V1, V2
392
393
        return getTrackPosition(trackId) + 1;
    }
394
    if (separated == 2) {
395
        // This will be A1, A2, V1, V2
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
        // Count audio/video tracks
        auto it = m_allTracks.cbegin();
        int aCount = 0;
        int vCount = 0;
        int refPos = 0;
        bool isVideo = true;
        while (it != m_allTracks.cend()) {
            if ((*it)->isAudioTrack()) {
                if ((*it)->getId() == trackId) {
                    refPos = aCount;
                    isVideo = false;
                }
                aCount++;
            } else {
                // video track
                if ((*it)->getId() == trackId) {
                    refPos = vCount;
                }
                vCount++;
            }
            ++it;
        }
        return isVideo ? aCount + refPos + 1 : aCount - refPos;
    }
420
    // This will be A1, V1, A2, V2
421
    auto it = m_allTracks.cend();
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
    int aCount = 0;
    int vCount = 0;
    bool isAudio = false;
    int trackPos = 0;
    while (it != m_allTracks.begin()) {
        --it;
        bool audioTrack = (*it)->isAudioTrack();
        if (audioTrack) {
            aCount++;
        } else {
            vCount++;
        }
        if (trackId == (*it)->getId()) {
            isAudio = audioTrack;
            trackPos = audioTrack ? aCount : vCount;
        }
    }
439
    if (isAudio) {
440
441
442
443
444
445
        if (aCount > vCount) {
            if (trackPos - 1 > aCount - vCount) {
                // We have more audio tracks than video tracks
                return (aCount - vCount + 1) + 2 * (trackPos - (aCount - vCount +1));
            }
            return trackPos;
446
        }
447
        return 2 * trackPos;
448
    }
449
    return 2 * (vCount + 1 - trackPos) + 1;
450
451
}

Nicolas Carion's avatar
Nicolas Carion committed
452
QList<int> TimelineModel::getLowerTracksId(int trackId, TrackType type) const
453
454
455
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
Nicolas Carion's avatar
Nicolas Carion committed
456
    QList<int> results;
457
    auto it = m_iteratorTable.at(trackId);
458
    while (it != m_allTracks.cbegin()) {
459
460
        --it;
        if (type == TrackType::AnyTrack) {
461
462
            results << (*it)->getId();
            continue;
463
        }
464
465
        bool audioTrack = (*it)->isAudioTrack();
        if (type == TrackType::AudioTrack && audioTrack) {
466
            results << (*it)->getId();
467
        } else if (type == TrackType::VideoTrack && !audioTrack) {
468
            results << (*it)->getId();
469
470
        }
    }
471
    return results;
472
473
}

474
475
476
477
478
int TimelineModel::getPreviousVideoTrackIndex(int trackId) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    auto it = m_iteratorTable.at(trackId);
479
    while (it != m_allTracks.cbegin()) {
480
        --it;
481
482
        if (!(*it)->isAudioTrack()) {
            return (*it)->getId();
483
484
        }
    }
485
    return 0;
486
487
}

488
int TimelineModel::getPreviousVideoTrackPos(int trackId) const
489
490
491
492
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    auto it = m_iteratorTable.at(trackId);
493
    while (it != m_allTracks.cbegin()) {
494
        --it;
495
496
        if (!(*it)->isAudioTrack()) {
            return getTrackMltIndex((*it)->getId());
497
        }
498
    }
499
    return 0;
500
501
}

502
503
504
505
506
507
508
509
510
511
int TimelineModel::getMirrorVideoTrackId(int trackId) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    auto it = m_iteratorTable.at(trackId);
    if (!(*it)->isAudioTrack()) {
        // we expected an audio track...
        return -1;
    }
    int count = 0;
512
    while (it != m_allTracks.cend()) {
513
514
515
        if ((*it)->isAudioTrack()) {
            count++;
        } else {
516
            count--;
517
518
519
520
521
522
523
524
525
            if (count == 0) {
                return (*it)->getId();
            }
        }
        ++it;
    }
    return -1;
}

526
527
528
529
530
531
532
533
int TimelineModel::getMirrorTrackId(int trackId) const
{
    if (isAudioTrack(trackId)) {
        return getMirrorVideoTrackId(trackId);
    }
    return getMirrorAudioTrackId(trackId);
}

534
535
536
537
538
539
540
int TimelineModel::getMirrorAudioTrackId(int trackId) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    auto it = m_iteratorTable.at(trackId);
    if ((*it)->isAudioTrack()) {
        // we expected a video track...
541
        qWarning() << "requesting mirror audio track for audio track";
542
543
544
        return -1;
    }
    int count = 0;
545
    while (it != m_allTracks.cbegin()) {
546
547
548
        if (!(*it)->isAudioTrack()) {
            count++;
        } else {
549
            count--;
550
551
552
553
            if (count == 0) {
                return (*it)->getId();
            }
        }
554
        --it;
555
    }
556
    if ((*it)->isAudioTrack() && count == 1) {
557
558
        return (*it)->getId();
    }
559
560
561
    return -1;
}

562
563
564
565
566
567
568
569
570
571
void TimelineModel::setEditMode(TimelineMode::EditMode mode)
{
    m_editMode = mode;
}

bool TimelineModel::normalEdit() const
{
    return m_editMode == TimelineMode::NormalEdit;
}

572
bool TimelineModel::requestFakeClipMove(int clipId, int trackId, int position, bool updateView, bool invalidateTimeline, Fun &undo, Fun &redo)
573
{
574
575
576
577
    Q_UNUSED(updateView);
    Q_UNUSED(invalidateTimeline);
    Q_UNUSED(undo);
    Q_UNUSED(redo);
578
579
580
581
582
    Q_ASSERT(isClip(clipId));
    m_allClips[clipId]->setFakePosition(position);
    bool trackChanged = false;
    if (trackId > -1) {
        if (trackId != m_allClips[clipId]->getFakeTrackId()) {
583
            if (getTrackById_const(trackId)->trackType() == m_allClips[clipId]->clipState()) {
584
585
586
587
588
589
590
                m_allClips[clipId]->setFakeTrackId(trackId);
                trackChanged = true;
            }
        }
    }
    QModelIndex modelIndex = makeClipIndexFromID(clipId);
    if (modelIndex.isValid()) {
591
        QVector<int> roles{FakePositionRole};
592
593
594
595
596
597
598
599
600
        if (trackChanged) {
            roles << FakeTrackIdRole;
        }
        notifyChange(modelIndex, modelIndex, roles);
        return true;
    }
    return false;
}

601
bool TimelineModel::requestClipMove(int clipId, int trackId, int position, bool moveMirrorTracks, bool updateView, bool invalidateTimeline, bool finalMove, Fun &undo, Fun &redo, bool groupMove, QMap <int, int> moving_clips)
602
{
Vincent Pinon's avatar
Vincent Pinon committed
603
    Q_UNUSED(moveMirrorTracks)
604
605
606
    if (trackId == -1) {
        return false;
    }
607
    Q_ASSERT(isClip(clipId));
608
609
610
611
612
613
614
615
    if (m_allClips[clipId]->clipState() == PlaylistState::Disabled) {
        if (getTrackById_const(trackId)->trackType() == PlaylistState::AudioOnly && !m_allClips[clipId]->canBeAudio()) {
            return false;
        }
        if (getTrackById_const(trackId)->trackType() == PlaylistState::VideoOnly && !m_allClips[clipId]->canBeVideo()) {
            return false;
        }
    } else if (getTrackById_const(trackId)->trackType() != m_allClips[clipId]->clipState()) {
616
617
618
        // Move not allowed (audio / video mismatch)
        return false;
    }
Nicolas Carion's avatar
Nicolas Carion committed
619
620
    std::function<bool(void)> local_undo = []() { return true; };
    std::function<bool(void)> local_redo = []() { return true; };
621
    bool ok = true;
622
    int old_trackId = getClipTrackId(clipId);
623
624
625
626
    int previous_track = moving_clips.value(clipId, -1);
    if (old_trackId == -1) {
        //old_trackId = previous_track;
    }
627
628
629
630
    bool notifyViewOnly = false;
    Fun update_model = []() { return true; };
    if (old_trackId == trackId) {
        // Move on same track, simply inform the view
Nicolas Carion's avatar
Nicolas Carion committed
631
        updateView = false;
632
        notifyViewOnly = true;
633
        update_model = [clipId, this, trackId, invalidateTimeline]() {
634
            QModelIndex modelIndex = makeClipIndexFromID(clipId);
Nicolas Carion's avatar
Nicolas Carion committed
635
            notifyChange(modelIndex, modelIndex, StartRole);
636
            if (invalidateTimeline && !getTrackById_const(trackId)->isAudioTrack()) {
637
638
639
                int in = getClipPosition(clipId);
                emit invalidateZone(in, in + getClipPlaytime(clipId));
            }
640
641
642
            return true;
        };
    }
643
644
645
646
647
    Fun sync_mix = []() { return true; };
    Fun update_playlist = []() { return true; };
    Fun update_playlist_undo = []() { return true; };
    Fun simple_move_mix = []() { return true; };
    Fun simple_restore_mix = []() { return true; };
648
    if (old_trackId == -1 && isTrack(previous_track) && getTrackById_const(previous_track)->hasMix(clipId) && previous_track != trackId) {
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
        // Clip is moved to another track
        std::pair<MixInfo, MixInfo> mixData = getTrackById_const(previous_track)->getMixInfo(clipId);
        bool mixGroupMove = false;
        if (m_groups->isInGroup(clipId)) {
            int parentGroup = m_groups->getRootId(clipId);
            if (parentGroup > -1) {
                std::unordered_set<int> sub = m_groups->getLeaves(parentGroup);
                if (sub.count(mixData.first.firstClipId) > 0 && sub.count(mixData.first.secondClipId) > 0) {
                    mixGroupMove = true;
                }
            }
        }
        if (mixGroupMove) {
            // We are moving a group on another track, delete and re-add
            bool isAudio = getTrackById_const(previous_track)->isAudioTrack();
            simple_move_mix = [this, previous_track, trackId, clipId, finalMove, mixData, isAudio]() {
                getTrackById_const(previous_track)->syncronizeMixes(finalMove);
                bool result = getTrackById_const(trackId)->createMix(mixData.first, isAudio);
                return result;
            };
            simple_restore_mix = [this, previous_track, trackId, finalMove, mixData, isAudio]() {
                bool result = true;
                if (finalMove) {
                    result = getTrackById_const(previous_track)->createMix(mixData.first, isAudio);
                    getTrackById_const(trackId)->syncronizeMixes(finalMove);
                    //getTrackById_const(previous_track)->syncronizeMixes(finalMove);
                }
                return result;
            };
        }
679
    } else if (finalMove && !groupMove && isTrack(old_trackId) && getTrackById_const(old_trackId)->hasMix(clipId)) {
680
        // Clip has a mix
681
682
        std::pair<MixInfo, MixInfo> mixData = getTrackById_const(old_trackId)->getMixInfo(clipId);
        if (mixData.first.firstClipId > -1) {
683
            // We have a mix at clip start
684
685
            sync_mix = [this, old_trackId, finalMove]() {
                getTrackById_const(old_trackId)->syncronizeMixes(finalMove);
686
687
                return true;
            };
688
            if (old_trackId == trackId) {
689
                // We are moving a clip on same track
690
                if (finalMove && position >= mixData.first.firstClipInOut.second) {
691
                    removeMixWithUndo(clipId, local_undo, local_redo);
692
                }
693
            } else if (finalMove) {
694
695
                // Clip moved to another track, delete mix
                int subPlaylist = m_allClips[clipId]->getSubPlaylistIndex();
696
                update_playlist = [this, clipId, old_trackId, trackId, finalMove]() {
697
                    m_allClips[clipId]->setMixDuration(0);
698
                    m_allClips[clipId]->setSubPlaylistIndex(0, trackId);
699
700
701
702
703
                    getTrackById_const(old_trackId)->syncronizeMixes(finalMove);
                    return true;
                };
                bool isAudio = getTrackById_const(old_trackId)->isAudioTrack();
                update_playlist_undo = [this, clipId, subPlaylist, mixData, old_trackId, isAudio, finalMove]() {
704
                    m_allClips[clipId]->setSubPlaylistIndex(subPlaylist, old_trackId);
705
706
707
                    bool result = getTrackById_const(old_trackId)->createMix(mixData.first, isAudio);
                    return result;
                };
708
            }
709
        }
710
711
        if (mixData.second.firstClipId > -1) {
            // We have a mix at clip end
712
713
714
            int clipDuration = mixData.second.firstClipInOut.second - mixData.second.firstClipInOut.first;
            sync_mix = [this, old_trackId, finalMove]() {
                getTrackById_const(old_trackId)->syncronizeMixes(finalMove);
715
716
                return true;
            };
717
718
            if (old_trackId == trackId) {
                if (finalMove && (position + clipDuration <= mixData.second.secondClipInOut.first)) {
719
                    // Moved outside mix zone
720
                    removeMixWithUndo(mixData.second.secondClipId, local_undo, local_redo);
721
                }
722
723
724
725
726
727
728
729
730
731
732
            } else {
                // Clip moved to another track, delete mix
                // Mix will be deleted by syncronizeMixes operation, only
                // re-add it on undo
                bool isAudio = getTrackById_const(old_trackId)->isAudioTrack();
                update_playlist_undo = [this, mixData, old_trackId, isAudio]() {
                    bool result = getTrackById_const(old_trackId)->createMix(mixData.second, isAudio);
                    return result;
                };
            }
        }
733
    } else if (finalMove && groupMove && isTrack(old_trackId) && getTrackById_const(old_trackId)->hasMix(clipId) && old_trackId == trackId) {
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
        std::pair<MixInfo, MixInfo> mixData = getTrackById_const(old_trackId)->getMixInfo(clipId);
        if (mixData.first.firstClipId > -1) {
            // Mix on clip start, check if mix is still in range
            if (!moving_clips.contains(mixData.first.firstClipId)) {
                int mixEnd = m_allClips[mixData.first.firstClipId]->getPosition() + m_allClips[mixData.first.firstClipId]->getPlaytime();
                if (mixEnd < position) {
                    // Mix will be deleted, recreate on undo
                    bool isAudio = getTrackById_const(old_trackId)->isAudioTrack();
                    update_playlist_undo = [this, mixData, old_trackId, isAudio]() {
                        bool result = getTrackById_const(old_trackId)->createMix(mixData.first, isAudio);
                        return result;
                    };
                }
            }
        }
        if (mixData.second.firstClipId > -1) {
            // Mix on clip end, check if mix is still in range
            if (!moving_clips.contains(mixData.second.secondClipId)) {
                int mixEnd = m_allClips[mixData.second.secondClipId]->getPosition();
                if (mixEnd > position + m_allClips[clipId]->getPlaytime()) {
                    // Mix will be deleted, recreate on undo
                    bool isAudio = getTrackById_const(old_trackId)->isAudioTrack();
                    update_playlist_undo = [this, mixData, old_trackId, isAudio]() {
                        bool result = getTrackById_const(old_trackId)->createMix(mixData.second, isAudio);
                        return result;
                    };
                }
761
762
            }
        }
763
    }
764
765
766
767
    PUSH_LAMBDA(simple_restore_mix, local_undo);
    if (finalMove) {
        PUSH_LAMBDA(sync_mix, local_undo);
    }
768
    if (old_trackId != -1) {
769
770
771
        if (notifyViewOnly) {
            PUSH_LAMBDA(update_model, local_undo);
        }
772
        ok = getTrackById(old_trackId)->requestClipDeletion(clipId, updateView, finalMove, local_undo, local_redo, groupMove, false);
773
        if (!ok) {
774
775
            bool undone = local_undo();
            Q_ASSERT(undone);
776
777
778
            return false;
        }
    }
779
780
    update_playlist();
    UPDATE_UNDO_REDO(update_playlist, update_playlist_undo, local_undo, local_redo);
781
    ok = getTrackById(trackId)->requestClipInsertion(clipId, position, updateView, finalMove, local_undo, local_redo, groupMove);
782
    if (!ok) {
783
        qWarning() << "clip insertion failed";
784
785
        bool undone = local_undo();
        Q_ASSERT(undone);
786
        return false;
787
    }
788
    sync_mix();
789
    update_model();
790
791
792
793
794
    simple_move_mix();
    PUSH_LAMBDA(simple_move_mix, local_redo);
    if (finalMove) {
        PUSH_LAMBDA(sync_mix, local_redo);
    }
795
796
797
    if (notifyViewOnly) {
        PUSH_LAMBDA(update_model, local_redo);
    }
Nicolas Carion's avatar
Nicolas Carion committed
798
799
    UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
    return true;
800
801
}

802
bool TimelineModel::mixClip(int idToMove, int delta)
803
804
{
    int selectedTrack = -1;
805
806
    std::unordered_set<int> initialSelection = getCurrentSelection();
    if (idToMove == -1 && initialSelection.empty()) {
807
808
809
810
        pCore->displayMessage(i18n("Select a clip to apply the mix"), InformationMessage, 500);
        return false;
    }
    std::pair<int, int> clipsToMix;
811
    int mixPosition = 0;
812
    int previousClip = -1;
813
    int noSpaceInClip = 0;
814
815
816
817
818
819
820
    if (idToMove != -1) {
        initialSelection = {idToMove};
        idToMove = -1;
    }
    for (int s : initialSelection) {
        if (!isClip(s)) {
            continue;
821
        }
822
823
824
        selectedTrack = getClipTrackId(s);
        if (selectedTrack == -1 || !isTrack(selectedTrack)) {
            continue;
825
        }
826
827
828
829
        mixPosition = getItemPosition(s);
        int clipDuration =  getItemPlaytime(s);    
        // Check if we have a clip before and/or after
        int nextClip = -1;
830
        previousClip = -1;
831
        // Check if clip already has a mix
832
        if (delta > -1 && getTrackById_const(selectedTrack)->hasStartMix(s)) {
833
834
835
836
            if (getTrackById_const(selectedTrack)->hasEndMix(s)) {
                continue;
            }
            nextClip = getTrackById_const(selectedTrack)->getClipByPosition(mixPosition + clipDuration + 1);
837
        } else if (delta < 1 && getTrackById_const(selectedTrack)->hasEndMix(s)) {
838
839
840
841
842
            previousClip = getTrackById_const(selectedTrack)->getClipByPosition(mixPosition - 1);
            if (previousClip > -1 && getTrackById_const(selectedTrack)->hasEndMix(previousClip)) {
                // Could happen if 2 clips before are mixed to full length
                previousClip = -1;
            }
843
        } else {
844
845
846
847
848
849
            if (delta < 1) {
                previousClip = getTrackById_const(selectedTrack)->getClipByPosition(mixPosition - 1);
            }
            if (delta > -1) {
                nextClip = getTrackById_const(selectedTrack)->getClipByPosition(mixPosition + clipDuration + 1);
            }
850
        }
851
852
853
854
855
856
857
858
859
860
861
        if (previousClip > -1 && nextClip > -1) {
            // We have a clip before and a clip after, check timeline cursor position to decide where to mix
            int cursor = pCore->getTimelinePosition();
            if (cursor < mixPosition + clipDuration / 2) {
                nextClip = -1;
            } else {
                previousClip = -1;
            }
        }
        if (nextClip == -1) {
            if (previousClip == -1) {
862
            // No clip to mix, abort
863
864
                continue;
            }
865
866
867
868
869
870
            // Make sure we have enough space in clip to resize
            int maxLength = m_allClips[previousClip]->getMaxDuration();
            if ((m_allClips[s]->getMaxDuration() > -1 && m_allClips[s]->getIn() < 2) || (maxLength > -1 && m_allClips[previousClip]->getOut() + 2 >= maxLength)) {
                noSpaceInClip = 1;
                continue;
            }
871
872
873
874
875
876
877
            // Mix at start of selected clip
            clipsToMix.first = previousClip;
            clipsToMix.second = s;
            idToMove = s;
            break;
        } else {
            // Mix at end of selected clip
878
879
880
881
882
883
            // Make sure we have enough space in clip to resize
            int maxLength = m_allClips[s]->getMaxDuration();
            if ((m_allClips[nextClip]->getMaxDuration() > -1 && m_allClips[nextClip]->getIn() < 2) || (maxLength > -1 && m_allClips[s]->getOut() + 2 >= maxLength)) {
                noSpaceInClip = 2;
                continue;
            }
884
885
886
887
888
            mixPosition += clipDuration;
            clipsToMix.first = s;
            clipsToMix.second = nextClip;
            idToMove = s;
            break;
889
890
        }
    }
891
    if (idToMove == -1 || !isClip(idToMove)) {
892
893
894
895
896
        if (noSpaceInClip > 0) {
            pCore->displayMessage(i18n("Not enough frames at clip %1 to apply the mix", noSpaceInClip == 1 ? i18n("start") : i18n("end")), InformationMessage, 500);
        } else {
            pCore->displayMessage(i18n("Select a clip to apply the mix"), InformationMessage, 500);
        }
897
898
899
        return false;
    }

900
901
902
903
904
905
906
907
908
909
910
911
    std::function<bool(void)> undo = []() { return true; };
    std::function<bool(void)> redo = []() { return true; };
    bool result = requestClipMix(clipsToMix, selectedTrack, mixPosition, true, true, true, undo,
 redo, false);
    if (result) {
        // Check if this is an AV split group
        if (m_groups->isInGroup(idToMove)) {
            int parentGroup = m_groups->getRootId(idToMove);
            if (parentGroup > -1 && m_groups->getType(parentGroup) == GroupType::AVSplit) {
                std::unordered_set<int> sub = m_groups->getLeaves(parentGroup);
                // Perform mix on split clip
                for (int current_id : sub) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
912
                    if (idToMove == current_id) {
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
                        continue;
                    }
                    int splitTrack = m_allClips[current_id]->getCurrentTrackId();
                    int splitId;
                    if (previousClip == -1) {
                        splitId = getTrackById_const(splitTrack)->getClipByPosition(mixPosition + 1);
                        clipsToMix.first = current_id;
                        clipsToMix.second = splitId;
                    } else {
                        splitId = getTrackById_const(splitTrack)->getClipByPosition(mixPosition - 1);
                        clipsToMix.first = splitId;
                        clipsToMix.second = current_id;
                    }
                    if (splitId > -1 && clipsToMix.first != clipsToMix.second) {
                        result = requestClipMix(clipsToMix, splitTrack, mixPosition, true, true, true, undo, redo, false);
                    }
                }
            }
        }
        pCore->pushUndo(undo, redo, i18n("Create mix"));
933
934
935
936
        // Reselect clips
        if (!initialSelection.empty()) {
            requestSetSelection(initialSelection);
        }
937
938
        return result;
    } else {
939
        qWarning() << "mix failed";
940
        undo();
941
942
943
        if (!initialSelection.empty()) {
            requestSetSelection(initialSelection);
        }
944
945
946
        return false;
    }
}
947

948
bool TimelineModel::requestClipMix(std::pair<int, int> clipIds, int trackId, int position, bool updateView, bool invalidateTimeline, bool finalMove, Fun &undo, Fun &redo, bool groupMove)
949
950
951
952
{
    if (trackId == -1) {
        return false;
    }
953
    Q_ASSERT(isClip(clipIds.first));
954
955
956
957
958
959
960
961
    std::function<bool(void)> local_undo = []() { return true; };
    std::function<bool(void)> local_redo = []() { return true; };
    bool ok = true;
    bool notifyViewOnly = false;
    Fun update_model = []() { return true; };
    // Move on same track, simply inform the view
    updateView = false;
    notifyViewOnly = true;
962
    int mixDuration = pCore->getDurationFromString(KdenliveSettings::mix_duration());
963
964
    update_model = [clipIds, this, trackId, position, invalidateTimeline, mixDuration]() {
        QModelIndex modelIndex = makeClipIndexFromID(clipIds.second);
965
        notifyChange(modelIndex, modelIndex, {StartRole,DurationRole});
966
        QModelIndex modelIndex2 = makeClipIndexFromID(clipIds.first);
967
        notifyChange(modelIndex2, modelIndex2, {DurationRole});
968
        if (invalidateTimeline && !getTrackById_const(trackId)->isAudioTrack()) {
969
            emit invalidateZone(position - mixDuration / 2, position + mixDuration);
970
971
972
973
974
975
        }
        return true;
    };
    if (notifyViewOnly) {
        PUSH_LAMBDA(update_model, local_undo);
    }
976
    ok = getTrackById(trackId)->requestClipMix(clipIds, mixDuration, updateView, finalMove, local_undo, local_redo, groupMove);
977
    if (!ok) {
978
        qWarning() << "mix failed, reverting";
979
980
981
982
983
984
985
986
987
988
989
990
991
        bool undone = local_undo();
        Q_ASSERT(undone);
        return false;
    }
    update_model();
    if (notifyViewOnly) {
        PUSH_LAMBDA(update_model, local_redo);
    }
    UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
    return ok;

}

992
993
994
bool TimelineModel::requestFakeClipMove(int clipId, int trackId, int position, bool updateView, bool logUndo, bool invalidateTimeline)
{
    QWriteLocker locker(&m_lock);
995
    TRACE(clipId, trackId, position, updateView, logUndo, invalidateTimeline)
996
997
998
999
1000
1001
1002
1003
1004
    Q_ASSERT(m_allClips.count(clipId) > 0);
    if (m_groups->isInGroup(clipId)) {
        // element is in a group.
        int groupId = m_groups->getRootId(clipId);
        int current_trackId = getClipTrackId(clipId);
        int track_pos1 = getTrackPosition(trackId);
        int track_pos2 = getTrackPosition(current_trackId);
        int delta_track = track_pos1 - track_pos2;
        int delta_pos = position - m_allClips[clipId]->getPosition();
1005
1006
1007
        bool res = requestFakeGroupMove(clipId, groupId, delta_track, delta_pos, updateView, logUndo);
        TRACE_RES(res);
        return res;
1008
1009
1010
    }
    std::function<bool(void)> undo = []() { return true; };
    std::function<bool(void)> redo = []() { return true; };
1011
    bool res = requestFakeClipMove(clipId, trackId, position, updateView, invalidateTimeline, undo, redo);
1012
1013
1014
    if (res && logUndo) {
        PUSH_UNDO(undo, redo, i18n("Move clip"));
    }
1015
    TRACE_RES(res);
1016
1017
1018
    return res;
}

1019
bool TimelineModel::requestClipMove(int clipId, int trackId, int position, bool moveMirrorTracks, bool updateView, bool logUndo, bool invalidateTimeline)
1020
{
1021
    QWriteLocker locker(&m_lock);
Nicolas Carion's avatar
Nicolas Carion committed
1022
    TRACE(clipId, trackId, position, updateView, logUndo, invalidateTimeline