timelinemodel.cpp 214 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 "snapmodel.hpp"
36
#include "timelinefunctions.hpp"
37

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

50
51
#include "macros.hpp"

Vincent Pinon's avatar
Vincent Pinon committed
52
53
#ifdef CRASH_AUTO_TEST
#include "logger.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
}
Vincent Pinon's avatar
Vincent Pinon committed
104
105
106
107
108
109
#else
#define TRACE_CONSTR(...)
#define TRACE_STATIC(...)
#define TRACE_RES(...)
#define TRACE(...)
#endif
Nicolas Carion's avatar
Nicolas Carion committed
110

111
int TimelineModel::next_id = 0;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
112
int TimelineModel::seekDuration = 30000;
113

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

141
    TRACE_CONSTR(this);
142
143
}

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

159
160
TimelineModel::~TimelineModel()
{
161
    std::vector<int> all_ids;
Nicolas Carion's avatar
Nicolas Carion committed
162
    for (auto tracks : m_iteratorTable) {
163
164
        all_ids.push_back(tracks.first);
    }
Nicolas Carion's avatar
Nicolas Carion committed
165
    for (auto tracks : all_ids) {
166
        deregisterTrack_lambda(tracks)();
167
    }
168
    for (const auto &clip : m_allClips) {
169
170
        clip.second->deregisterClipToBin();
    }
171
172
}

173
174
int TimelineModel::getTracksCount() const
{
175
    READ_LOCK();
176
    int count = m_tractor->count();
177
178
    if (m_overlayTrackCount > -1) {
        count -= m_overlayTrackCount;
179
    }
180
    Q_ASSERT(count >= 0);
181
    // don't count the black background track
Nicolas Carion's avatar
Nicolas Carion committed
182
    Q_ASSERT(count - 1 == static_cast<int>(m_allTracks.size()));
183
    return count - 1;
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
211
212
213
214
215
216
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;
}

217
218
int TimelineModel::getTrackIndexFromPosition(int pos) const
{
219
    Q_ASSERT(pos >= 0 && pos < int(m_allTracks.size()));
220
    READ_LOCK();
221
    auto it = m_allTracks.cbegin();
222
223
224
225
226
227
228
    while (pos > 0) {
        it++;
        pos--;
    }
    return (*it)->getId();
}

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

236
237
238
239
240
241
int TimelineModel::getCompositionsCount() const
{
    READ_LOCK();
    int size = int(m_allCompositions.size());
    return size;
}
242

243
int TimelineModel::getClipTrackId(int clipId) const
244
{
245
    READ_LOCK();
246
247
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
248
    return clip->getCurrentTrackId();
249
250
}

251
252
253
254
255
256
257
258
259
260
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();
261
    Q_ASSERT(isItem(itemId));
262
263
264
    if (isClip(itemId)) {
        return getClipTrackId(itemId);
    }
265
266
267
    if (isComposition(itemId)) {
        return getCompositionTrackId(itemId);
    }
268
    return -1;
269
270
}

271
int TimelineModel::getClipPosition(int clipId) const
272
{
273
    READ_LOCK();
274
275
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
276
277
    int pos = clip->getPosition();
    return pos;
278
279
}

280
281
282
283
284
285
286
double TimelineModel::getClipSpeed(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    return m_allClips.at(clipId)->getSpeed();
}

287
288
289
290
291
292
293
int TimelineModel::getClipSplitPartner(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    return m_groups->getSplitPartner(clipId);
}

294
295
296
297
298
int TimelineModel::getClipIn(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(m_allClips.count(clipId) > 0);
    const auto clip = m_allClips.at(clipId);
299
300
301
302
303
304
305
306
307
    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();
308
309
310
311
312
313
314
315
316
317
318
}

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

319
int TimelineModel::getClipPlaytime(int clipId) const
320
{
321
    READ_LOCK();
322
    Q_ASSERT(isClip(clipId));
323
    const auto clip = m_allClips.at(clipId);
324
325
    int playtime = clip->getPlaytime();
    return playtime;
326
327
}

328
329
330
331
332
333
334
335
QSize TimelineModel::getClipFrameSize(int clipId) const
{
    READ_LOCK();
    Q_ASSERT(isClip(clipId));
    const auto clip = m_allClips.at(clipId);
    return clip->getFrameSize();
}

336
int TimelineModel::getTrackClipsCount(int trackId) const
337
{
338
    READ_LOCK();
339
    Q_ASSERT(isTrack(trackId));
340
    int count = getTrackById_const(trackId)->getClipsCount();
341
    return count;
342
343
}

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

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

358
359
360
361
362
363
364
int TimelineModel::getCompositionByPosition(int trackId, int position) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    return getTrackById_const(trackId)->getCompositionByPosition(position);
}

365
int TimelineModel::getSubtitleByStartPosition(int position) const
366
367
368
369
370
371
372
373
374
375
376
377
{
    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;
}

378
379
380
381
382
383
384
385
386
387
388
389
390
int TimelineModel::getSubtitleByPosition(int position) const
{
    READ_LOCK();
    GenTime startTime(position, pCore->getCurrentFps());
    if (m_subtitleModel) {
        std::unordered_set<int> sids = m_subtitleModel->getItemsInRange(position, position);
        if (!sids.empty()) {
            return *sids.begin();
        }
    }
    return -1;
}

391
int TimelineModel::getTrackPosition(int trackId) const
392
{
393
    READ_LOCK();
394
    Q_ASSERT(isTrack(trackId));
395
    auto it = m_allTracks.cbegin();
Vincent Pinon's avatar
Vincent Pinon committed
396
    int pos = int(std::distance(it, static_cast<decltype(it)>(m_iteratorTable.at(trackId))));
397
    return pos;
398
399
}

400
401
402
403
404
405
406
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;
}

407
int TimelineModel::getTrackSortValue(int trackId, int separated) const
408
{
409
    if (separated == 1) {
410
        // This will be A2, A1, V1, V2
411
412
        return getTrackPosition(trackId) + 1;
    }
413
    if (separated == 2) {
414
        // This will be A1, A2, V1, V2
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
        // 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;
    }
439
    // This will be A1, V1, A2, V2
440
    auto it = m_allTracks.cend();
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
    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;
        }
    }
458
    if (isAudio) {
459
460
461
462
463
464
        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;
465
        }
466
        return 2 * trackPos;
467
    }
468
    return 2 * (vCount + 1 - trackPos) + 1;
469
470
}

Nicolas Carion's avatar
Nicolas Carion committed
471
QList<int> TimelineModel::getLowerTracksId(int trackId, TrackType type) const
472
473
474
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
Nicolas Carion's avatar
Nicolas Carion committed
475
    QList<int> results;
476
    auto it = m_iteratorTable.at(trackId);
477
    while (it != m_allTracks.cbegin()) {
478
479
        --it;
        if (type == TrackType::AnyTrack) {
480
481
            results << (*it)->getId();
            continue;
482
        }
483
484
        bool audioTrack = (*it)->isAudioTrack();
        if (type == TrackType::AudioTrack && audioTrack) {
485
            results << (*it)->getId();
486
        } else if (type == TrackType::VideoTrack && !audioTrack) {
487
            results << (*it)->getId();
488
489
        }
    }
490
    return results;
491
492
}

493
494
495
496
497
int TimelineModel::getPreviousVideoTrackIndex(int trackId) const
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    auto it = m_iteratorTable.at(trackId);
498
    while (it != m_allTracks.cbegin()) {
499
        --it;
500
501
        if (!(*it)->isAudioTrack()) {
            return (*it)->getId();
502
503
        }
    }
504
    return 0;
505
506
}

507
int TimelineModel::getPreviousVideoTrackPos(int trackId) const
508
509
510
511
{
    READ_LOCK();
    Q_ASSERT(isTrack(trackId));
    auto it = m_iteratorTable.at(trackId);
512
    while (it != m_allTracks.cbegin()) {
513
        --it;
514
515
        if (!(*it)->isAudioTrack()) {
            return getTrackMltIndex((*it)->getId());
516
        }
517
    }
518
    return 0;
519
520
}

521
522
523
524
525
526
527
528
529
530
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;
531
    while (it != m_allTracks.cend()) {
532
533
534
        if ((*it)->isAudioTrack()) {
            count++;
        } else {
535
            count--;
536
537
538
539
540
541
542
543
544
            if (count == 0) {
                return (*it)->getId();
            }
        }
        ++it;
    }
    return -1;
}

545
546
547
548
549
550
551
552
int TimelineModel::getMirrorTrackId(int trackId) const
{
    if (isAudioTrack(trackId)) {
        return getMirrorVideoTrackId(trackId);
    }
    return getMirrorAudioTrackId(trackId);
}

553
554
555
556
557
558
559
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...
560
        qWarning() << "requesting mirror audio track for audio track";
561
562
563
        return -1;
    }
    int count = 0;
564
    while (it != m_allTracks.cbegin()) {
565
566
567
        if (!(*it)->isAudioTrack()) {
            count++;
        } else {
568
            count--;
569
570
571
572
            if (count == 0) {
                return (*it)->getId();
            }
        }
573
        --it;
574
    }
575
    if ((*it)->isAudioTrack() && count == 1) {
576
577
        return (*it)->getId();
    }
578
579
580
    return -1;
}

581
582
583
584
585
586
587
588
589
590
void TimelineModel::setEditMode(TimelineMode::EditMode mode)
{
    m_editMode = mode;
}

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

591
bool TimelineModel::requestFakeClipMove(int clipId, int trackId, int position, bool updateView, bool invalidateTimeline, Fun &undo, Fun &redo)
592
{
593
594
595
596
    Q_UNUSED(updateView);
    Q_UNUSED(invalidateTimeline);
    Q_UNUSED(undo);
    Q_UNUSED(redo);
597
598
599
600
601
    Q_ASSERT(isClip(clipId));
    m_allClips[clipId]->setFakePosition(position);
    bool trackChanged = false;
    if (trackId > -1) {
        if (trackId != m_allClips[clipId]->getFakeTrackId()) {
602
            if (getTrackById_const(trackId)->trackType() == m_allClips[clipId]->clipState()) {
603
604
605
606
607
608
609
                m_allClips[clipId]->setFakeTrackId(trackId);
                trackChanged = true;
            }
        }
    }
    QModelIndex modelIndex = makeClipIndexFromID(clipId);
    if (modelIndex.isValid()) {
610
        QVector<int> roles{FakePositionRole};
611
612
613
614
615
616
617
618
619
        if (trackChanged) {
            roles << FakeTrackIdRole;
        }
        notifyChange(modelIndex, modelIndex, roles);
        return true;
    }
    return false;
}

620
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, std::pair<MixInfo,MixInfo>mixData)
621
{
Vincent Pinon's avatar
Vincent Pinon committed
622
    Q_UNUSED(moveMirrorTracks)
623
624
625
    if (trackId == -1) {
        return false;
    }
626
    Q_ASSERT(isClip(clipId));
627
628
629
630
631
632
633
634
    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()) {
635
636
637
        // Move not allowed (audio / video mismatch)
        return false;
    }
Nicolas Carion's avatar
Nicolas Carion committed
638
639
    std::function<bool(void)> local_undo = []() { return true; };
    std::function<bool(void)> local_redo = []() { return true; };
640
    bool ok = true;
641
    int old_trackId = getClipTrackId(clipId);
642
643
644
645
    int previous_track = moving_clips.value(clipId, -1);
    if (old_trackId == -1) {
        //old_trackId = previous_track;
    }
646
647
648
649
    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
650
        updateView = false;
651
        notifyViewOnly = true;
652
        update_model = [clipId, this, trackId, invalidateTimeline]() {
653
            QModelIndex modelIndex = makeClipIndexFromID(clipId);
Nicolas Carion's avatar
Nicolas Carion committed
654
            notifyChange(modelIndex, modelIndex, StartRole);
655
            if (invalidateTimeline && !getTrackById_const(trackId)->isAudioTrack()) {
656
657
658
                int in = getClipPosition(clipId);
                emit invalidateZone(in, in + getClipPlaytime(clipId));
            }
659
660
661
            return true;
        };
    }
662
663
664
    Fun sync_mix = []() { return true; };
    Fun simple_move_mix = []() { return true; };
    Fun simple_restore_mix = []() { return true; };
665
    QList<int> allowedClipMixes;
666
667
668
    if (!groupMove && old_trackId > -1) {
        mixData = getTrackById_const(old_trackId)->getMixInfo(clipId);
    }
669
670
    bool hadMix = mixData.first.firstClipId > -1 || mixData.second.secondClipId > -1;
    if (old_trackId == -1 && isTrack(previous_track) && hadMix && previous_track != trackId) {
671
672
        // Clip is moved to another track
        bool mixGroupMove = false;
673
674
        if (mixData.first.firstClipId > 0) {
            allowedClipMixes << mixData.first.firstClipId;
675
676
677
678
679
            if (moving_clips.contains(mixData.first.firstClipId)) {
                allowedClipMixes << mixData.first.firstClipId;
            } else if (finalMove) {
                removeMixWithUndo(clipId, local_undo, local_redo);
            }
680
681
682
        }
        if (mixData.second.firstClipId > 0) {
            allowedClipMixes << mixData.second.secondClipId;
683
684
685
686
687
            if (moving_clips.contains(mixData.second.secondClipId)) {
                allowedClipMixes << mixData.second.secondClipId;
            } else if (finalMove) {
                removeMixWithUndo(mixData.second.secondClipId, local_undo, local_redo);
            }
688
        }
689
        if (m_groups->isInGroup(clipId) && mixData.first.firstClipId > 0) {
690
691
692
693
694
695
696
697
698
699
            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
700
            // Get mix properties
701
702
703
704
            std::pair<QString,QVector<QPair<QString, QVariant>>> mixParams = getTrackById_const(previous_track)->getMixParams(mixData.first.secondClipId);
            simple_move_mix = [this, previous_track, trackId, finalMove, mixData, mixParams]() {
                // Insert mix on new track
                bool result = getTrackById_const(trackId)->createMix(mixData.first, mixParams, finalMove);
705
706
                // Remove mix on old track
                getTrackById_const(previous_track)->syncronizeMixes(finalMove);
707
708
                return result;
            };
709
710
            simple_restore_mix = [this, previous_track, trackId, finalMove, mixData, mixParams]() {
                bool result = getTrackById_const(previous_track)->createMix(mixData.first, mixParams, finalMove);
711
                getTrackById_const(trackId)->syncronizeMixes(finalMove);
712
713
714
                return result;
            };
        }
715
    } else if (finalMove && !groupMove && isTrack(old_trackId) && hadMix) {
716
        // Clip has a mix
717
        if (mixData.first.firstClipId > -1) {
718
            if (old_trackId == trackId) {
719
                // We are moving a clip on same track
720
                if (position >= mixData.first.firstClipInOut.second) {
721
                    position += m_allClips[clipId]->getMixDuration() - m_allClips[clipId]->getMixCutPosition();
722
                    removeMixWithUndo(clipId, local_undo, local_redo);
723
                }
724
            } else {
725
                // Clip moved to another track, delete mix
726
                removeMixWithUndo(clipId, local_undo, local_redo);
727
            }
728
        }
729
730
        if (mixData.second.firstClipId > -1) {
            // We have a mix at clip end
731
732
733
            int clipDuration = mixData.second.firstClipInOut.second - mixData.second.firstClipInOut.first;
            sync_mix = [this, old_trackId, finalMove]() {
                getTrackById_const(old_trackId)->syncronizeMixes(finalMove);
734
735
                return true;
            };
736
737
            if (old_trackId == trackId) {
                if (finalMove && (position + clipDuration <= mixData.second.secondClipInOut.first)) {
738
                    // Moved outside mix zone
739
                    removeMixWithUndo(mixData.second.secondClipId, local_undo, local_redo);
740
                }
741
742
743
744
            } else {
                // Clip moved to another track, delete mix
                // Mix will be deleted by syncronizeMixes operation, only
                // re-add it on undo
745
                removeMixWithUndo(mixData.second.secondClipId, local_undo, local_redo);
746
747
            }
        }
748
    } else if (finalMove && groupMove && isTrack(old_trackId) && hadMix && old_trackId == trackId) {
749
750
751
752
753
754
        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
755
756
                    position += m_allClips[mixData.first.secondClipId]->getMixDuration() - m_allClips[mixData.first.secondClipId]->getMixCutPosition();
                    removeMixWithUndo(mixData.first.secondClipId, local_undo, local_redo);
757
                }
758
759
            } else {
                allowedClipMixes << mixData.first.firstClipId;
760
761
762
763
764
765
766
767
            }
        }
        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
768
                    removeMixWithUndo(mixData.second.secondClipId, local_undo, local_redo);
769
                }
770
771
            } else {
                allowedClipMixes << mixData.second.secondClipId;
772
773
            }
        }
774
    }
775
    if (old_trackId != -1) {
776
777
778
        if (notifyViewOnly) {
            PUSH_LAMBDA(update_model, local_undo);
        }
779
        ok = getTrackById(old_trackId)->requestClipDeletion(clipId, updateView, finalMove, local_undo, local_redo, groupMove, false, allowedClipMixes);
780
        if (!ok) {
781
782
            bool undone = local_undo();
            Q_ASSERT(undone);
783
784
785
            return false;
        }
    }
786
    ok = ok && getTrackById(trackId)->requestClipInsertion(clipId, position, updateView, finalMove, local_undo, local_redo, groupMove, allowedClipMixes);
787
    if (!ok) {
788
        qWarning() << "clip insertion failed";
789
790
        bool undone = local_undo();
        Q_ASSERT(undone);
791
        return false;
792
    }
793
    sync_mix();
794
    update_model();
795
796
    simple_move_mix();
    if (finalMove) {
797
798
799
        PUSH_LAMBDA(simple_restore_mix, undo);
        PUSH_LAMBDA(simple_move_mix, local_redo);
        //PUSH_LAMBDA(sync_mix, local_redo);
800
    }
801
802
803
    if (notifyViewOnly) {
        PUSH_LAMBDA(update_model, local_redo);
    }
Nicolas Carion's avatar
Nicolas Carion committed
804
805
    UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
    return true;
806
807
}

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

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

954
bool TimelineModel::requestClipMix(std::pair<int, int> clipIds, int trackId, int position, bool updateView, bool invalidateTimeline, bool finalMove, Fun &undo, Fun &redo, bool groupMove)
955
956
957
958
{
    if (trackId == -1) {
        return false;
    }
959
    Q_ASSERT(isClip(clipIds.first));
960
961
962
963
964
965
966
967
    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;
968
    int mixDuration = pCore->getDurationFromString(KdenliveSettings::mix_duration());
Jean-Baptiste Mardelle's avatar