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 "groupsmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
34
#include "kdenlivesettings.h"
35
#include "logger.hpp"
36
#include "snapmodel.hpp"
37
#include "timelinefunctions.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
38
#include "trackmodel.hpp"
39

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

51
52
#include "macros.hpp"

Nicolas Carion's avatar
Nicolas Carion committed
53
54
55
56
57
58
59
60
61
62
63
64
#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")
65
        .method("setTrackLockedState", &TimelineModel::setTrackLockedState)(parameter_names("trackId", "lock"))
66
67
        .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
68
69
70
71
72
        .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"))
73
74
        .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
75
76
77
78
        .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))(
79
80
81
            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
82
83
        .method("requestTrackInsertion", select_overload<bool(int, int &, const QString &, bool)>(&TimelineModel::requestTrackInsertion))(
            parameter_names("pos", "id", "trackName", "audioTrack"))
84
        .method("requestTrackDeletion", select_overload<bool(int)>(&TimelineModel::requestTrackDeletion))(parameter_names("trackId"))
85
        .method("requestClearSelection", select_overload<bool(bool)>(&TimelineModel::requestClearSelection))(parameter_names("onDeletion"))
86
87
        .method("requestAddToSelection", &TimelineModel::requestAddToSelection)(parameter_names("itemId", "clear"))
        .method("requestRemoveFromSelection", &TimelineModel::requestRemoveFromSelection)(parameter_names("itemId"))
88
89
90
        .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"))
91
92
        .method("requestFakeGroupMove", select_overload<bool(int, int, int, int, bool, bool)>(&TimelineModel::requestFakeGroupMove))(
            parameter_names("clipId", "groupId", "delta_track", "delta_pos", "updateView", "logUndo"))
93
        .method("suggestClipMove", &TimelineModel::suggestClipMove)(parameter_names("clipId", "trackId", "position", "cursorPosition", "snapDistance", "moveMirrorTracks"))
94
95
        .method("suggestCompositionMove",
                &TimelineModel::suggestCompositionMove)(parameter_names("compoId", "trackId", "position", "cursorPosition", "snapDistance"))
96
97
        // .method("addSnap", &TimelineModel::addSnap)(parameter_names("pos"))
        // .method("removeSnap", &TimelineModel::addSnap)(parameter_names("pos"))
98
99
100
        // .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"))
101
        .method("requestClipTimeWarp", select_overload<bool(int, double,bool,bool)>(&TimelineModel::requestClipTimeWarp))(parameter_names("clipId", "speed","pitchCompensate","changeDuration"));
Nicolas Carion's avatar
Nicolas Carion committed
102
103
}

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

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

134
    TRACE_CONSTR(this);
135
136
}

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

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

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

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

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

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

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

244
245
246
247
248
249
250
251
252
253
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();
254
    Q_ASSERT(isItem(itemId));
255
256
257
    if (isClip(itemId)) {
        return getClipTrackId(itemId);
    }
258
259
260
    if (isComposition(itemId)) {
        return getCompositionTrackId(itemId);
    }
261
    return -1;
262
263
}

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

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

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

287
288
289
290
291
int TimelineModel::getClipIn(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
292
293
294
295
296
297
298
299
300
    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();
301
302
303
304
305
306
307
308
309
310
311
}

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

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

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

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

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

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

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

358
359
360
361
362
363
364
365
366
367
368
369
370
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;
}

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

380
381
382
383
384
385
386
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;
}

387
int TimelineModel::getTrackSortValue(int trackId, int separated) const
388
{
389
    if (separated == 1) {
390
        // This will be A2, A1, V1, V2
391
392
        return getTrackPosition(trackId) + 1;
    }
393
    if (separated == 2) {
394
        // This will be A1, A2, V1, V2
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
        // 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;
    }
419
    // This will be A1, V1, A2, V2
420
    auto it = m_allTracks.cend();
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
    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;
        }
    }
438
    if (isAudio) {
439
440
441
442
443
444
        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;
445
        }
446
        return 2 * trackPos;
447
    }
448
    return 2 * (vCount + 1 - trackPos) + 1;
449
450
}

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

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

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

501
502
503
504
505
506
507
508
509
510
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;
511
    while (it != m_allTracks.cend()) {
512
513
514
        if ((*it)->isAudioTrack()) {
            count++;
        } else {
515
            count--;
516
517
518
519
520
521
522
523
524
            if (count == 0) {
                return (*it)->getId();
            }
        }
        ++it;
    }
    return -1;
}

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

533
534
535
536
537
538
539
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...
540
        qWarning() << "requesting mirror audio track for audio track";
541
542
543
        return -1;
    }
    int count = 0;
544
    while (it != m_allTracks.cbegin()) {
545
546
547
        if (!(*it)->isAudioTrack()) {
            count++;
        } else {
548
            count--;
549
550
551
552
            if (count == 0) {
                return (*it)->getId();
            }
        }
553
        --it;
554
    }
555
    if ((*it)->isAudioTrack() && count == 1) {
556
557
        return (*it)->getId();
    }
558
559
560
    return -1;
}

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

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

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

600
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)
601
{
Vincent Pinon's avatar
Vincent Pinon committed
602
    Q_UNUSED(moveMirrorTracks)
603
604
605
    if (trackId == -1) {
        return false;
    }
606
    Q_ASSERT(isClip(clipId));
607
608
609
610
611
612
613
614
    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()) {
615
616
617
        // Move not allowed (audio / video mismatch)
        return false;
    }
Nicolas Carion's avatar
Nicolas Carion committed
618
619
    std::function<bool(void)> local_undo = []() { return true; };
    std::function<bool(void)> local_redo = []() { return true; };
620
    bool ok = true;
621
    int old_trackId = getClipTrackId(clipId);
622
623
624
625
    int previous_track = moving_clips.value(clipId, -1);
    if (old_trackId == -1) {
        //old_trackId = previous_track;
    }
626
627
628
629
    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
630
        updateView = false;
631
        notifyViewOnly = true;
632
        update_model = [clipId, this, trackId, invalidateTimeline]() {
633
            QModelIndex modelIndex = makeClipIndexFromID(clipId);
Nicolas Carion's avatar
Nicolas Carion committed
634
            notifyChange(modelIndex, modelIndex, StartRole);
635
            if (invalidateTimeline && !getTrackById_const(trackId)->isAudioTrack()) {
636
637
638
                int in = getClipPosition(clipId);
                emit invalidateZone(in, in + getClipPlaytime(clipId));
            }
639
640
641
            return true;
        };
    }
642
643
644
645
646
    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; };
647
    if (old_trackId == -1 && isTrack(previous_track) && getTrackById_const(previous_track)->hasMix(clipId) && previous_track != trackId) {
648
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
        // 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;
            };
        }
678
    } else if (finalMove && !groupMove && isTrack(old_trackId) && getTrackById_const(old_trackId)->hasMix(clipId)) {
679
        // Clip has a mix
680
681
        std::pair<MixInfo, MixInfo> mixData = getTrackById_const(old_trackId)->getMixInfo(clipId);
        if (mixData.first.firstClipId > -1) {
682
            // We have a mix at clip start
683
684
            sync_mix = [this, old_trackId, finalMove]() {
                getTrackById_const(old_trackId)->syncronizeMixes(finalMove);
685
686
                return true;
            };
687
            if (old_trackId == trackId) {
688
                // We are moving a clip on same track
689
                if (finalMove && position >= mixData.first.firstClipInOut.second) {
690
                    removeMixWithUndo(clipId, local_undo, local_redo);
691
                }
692
            } else if (finalMove) {
693
694
                // Clip moved to another track, delete mix
                int subPlaylist = m_allClips[clipId]->getSubPlaylistIndex();
695
                update_playlist = [this, clipId, old_trackId, trackId, finalMove]() {
696
                    m_allClips[clipId]->setMixDuration(0);
697
                    m_allClips[clipId]->setSubPlaylistIndex(0, trackId);
698
699
700
701
702
                    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]() {
703
                    m_allClips[clipId]->setSubPlaylistIndex(subPlaylist, old_trackId);
704
705
706
                    bool result = getTrackById_const(old_trackId)->createMix(mixData.first, isAudio);
                    return result;
                };
707
            }
708
        }
709
710
        if (mixData.second.firstClipId > -1) {
            // We have a mix at clip end
711
712
713
            int clipDuration = mixData.second.firstClipInOut.second - mixData.second.firstClipInOut.first;
            sync_mix = [this, old_trackId, finalMove]() {
                getTrackById_const(old_trackId)->syncronizeMixes(finalMove);
714
715
                return true;
            };
716
717
            if (old_trackId == trackId) {
                if (finalMove && (position + clipDuration <= mixData.second.secondClipInOut.first)) {
718
                    // Moved outside mix zone
719
                    removeMixWithUndo(mixData.second.secondClipId, local_undo, local_redo);
720
                }
721
722
723
724
725
726
727
728
729
730
731
            } 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;
                };
            }
        }
732
    } else if (finalMove && groupMove && isTrack(old_trackId) && getTrackById_const(old_trackId)->hasMix(clipId) && old_trackId == trackId) {
733
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
        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;
                    };
                }
760
761
            }
        }
762
    }
763
764
765
766
    PUSH_LAMBDA(simple_restore_mix, local_undo);
    if (finalMove) {
        PUSH_LAMBDA(sync_mix, local_undo);
    }
767
    if (old_trackId != -1) {
768
769
770
        if (notifyViewOnly) {
            PUSH_LAMBDA(update_model, local_undo);
        }
771
        ok = getTrackById(old_trackId)->requestClipDeletion(clipId, updateView, finalMove, local_undo, local_redo, groupMove, false);
772
        if (!ok) {
773
774
            bool undone = local_undo();
            Q_ASSERT(undone);
775
776
777
            return false;
        }
    }
778
779
    update_playlist();
    UPDATE_UNDO_REDO(update_playlist, update_playlist_undo, local_undo, local_redo);
780
    ok = getTrackById(trackId)->requestClipInsertion(clipId, position, updateView, finalMove, local_undo, local_redo, groupMove);
781
    if (!ok) {
782
        qWarning() << "clip insertion failed";
783
784
        bool undone = local_undo();
        Q_ASSERT(undone);
785
        return false;
786
    }
787
    sync_mix();
788
    update_model();
789
790
791
792
793
    simple_move_mix();
    PUSH_LAMBDA(simple_move_mix, local_redo);
    if (finalMove) {
        PUSH_LAMBDA(sync_mix, local_redo);
    }
794
795
796
    if (notifyViewOnly) {
        PUSH_LAMBDA(update_model, local_redo);
    }
Nicolas Carion's avatar
Nicolas Carion committed
797
798
    UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
    return true;
799
800
}

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

899
900
901
902
903
904
905
906
907
908
909
910
    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
911
                    if (idToMove == current_id) {
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
                        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"));
932
933
934
935
        // Reselect clips
        if (!initialSelection.empty()) {
            requestSetSelection(initialSelection);
        }
936
937
        return result;
    } else {
938
        qWarning() << "mix failed";
939
        undo();
940
941
942
        if (!initialSelection.empty()) {
            requestSetSelection(initialSelection);
        }
943
944
945
        return false;
    }
}
946

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

}

991
992
993
bool TimelineModel::requestFakeClipMove(int clipId, int trackId, int position, bool updateView, bool logUndo, bool invalidateTimeline)
{
    QWriteLocker locker(&m_lock);
994
    TRACE(clipId, trackId, position, updateView, logUndo, invalidateTimeline)
Jean-Baptiste Mardelle's avatar