File indexing completed on 2024-04-28 04:52:36

0001 /*
0002     SPDX-FileCopyrightText: 2017 Nicolas Carion
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "trackmodel.hpp"
0007 #include "clipmodel.hpp"
0008 #include "compositionmodel.hpp"
0009 #include "core.h"
0010 #include "effects/effectstack/model/effectstackmodel.hpp"
0011 #include "kdenlivesettings.h"
0012 #include "transitions/transitionsrepository.hpp"
0013 #ifdef CRASH_AUTO_TEST
0014 #include "logger.hpp"
0015 #else
0016 #define TRACE_CONSTR(...)
0017 #endif
0018 #include "snapmodel.hpp"
0019 #include "timelinemodel.hpp"
0020 #include <QDebug>
0021 #include <QModelIndex>
0022 #include <memory>
0023 #include <mlt++/MltTransition.h>
0024 
0025 TrackModel::TrackModel(const std::weak_ptr<TimelineModel> &parent, int id, const QString &trackName, bool audioTrack)
0026     : m_parent(parent)
0027     , m_id(id == -1 ? TimelineModel::getNextId() : id)
0028     , m_lock(QReadWriteLock::Recursive)
0029     , m_softDelete(false)
0030 {
0031     if (auto ptr = parent.lock()) {
0032         m_track = std::make_shared<Mlt::Tractor>(pCore->getProjectProfile());
0033         m_playlists[0].set_profile(pCore->getProjectProfile().get_profile());
0034         m_playlists[1].set_profile(pCore->getProjectProfile().get_profile());
0035         m_track->insert_track(m_playlists[0], 0);
0036         m_track->insert_track(m_playlists[1], 1);
0037         if (!trackName.isEmpty()) {
0038             m_track->set("kdenlive:track_name", trackName.toUtf8().constData());
0039         }
0040         if (audioTrack) {
0041             m_track->set("kdenlive:audio_track", 1);
0042             for (auto &m_playlist : m_playlists) {
0043                 m_playlist.set("hide", 1);
0044             }
0045         } else {
0046             // Video track
0047             for (auto &m_playlist : m_playlists) {
0048                 m_playlist.set("hide", 2);
0049             }
0050         }
0051         // For now we never use the second playlist, only planned for same track transitions
0052         m_track->set("kdenlive:trackheight", KdenliveSettings::trackheight());
0053         m_track->set("kdenlive:timeline_active", 1);
0054         m_effectStack = EffectStackModel::construct(m_track, ObjectId(KdenliveObjectType::TimelineTrack, m_id, ptr->uuid()), ptr->m_undoStack);
0055         // TODO
0056         // When we use the second playlist, register it's stask as child of main playlist effectstack
0057         // m_subPlaylist = std::make_shared<Mlt::Producer>(&m_playlists[1]);
0058         // m_effectStack->addService(m_subPlaylist);
0059         QObject::connect(m_effectStack.get(), &EffectStackModel::dataChanged, [&](const QModelIndex &, const QModelIndex &, const QVector<int> &roles) {
0060             if (auto ptr2 = m_parent.lock()) {
0061                 QModelIndex ix = ptr2->makeTrackIndexFromID(m_id);
0062                 qDebug() << "==== TRACK ZONES CHANGED";
0063                 Q_EMIT ptr2->dataChanged(ix, ix, roles);
0064             }
0065         });
0066     } else {
0067         qDebug() << "Error : construction of track failed because parent timeline is not available anymore";
0068         Q_ASSERT(false);
0069     }
0070 }
0071 
0072 TrackModel::TrackModel(const std::weak_ptr<TimelineModel> &parent, Mlt::Tractor mltTrack, int id)
0073     : m_parent(parent)
0074     , m_id(id == -1 ? TimelineModel::getNextId() : id)
0075     , m_softDelete(false)
0076 {
0077     if (auto ptr = parent.lock()) {
0078         m_track = std::make_shared<Mlt::Tractor>(mltTrack);
0079         m_playlists[0] = *m_track->track(0);
0080         m_playlists[1] = *m_track->track(1);
0081         m_effectStack = EffectStackModel::construct(m_track, ObjectId(KdenliveObjectType::TimelineTrack, m_id, ptr->uuid()), ptr->m_undoStack);
0082     } else {
0083         qDebug() << "Error : construction of track failed because parent timeline is not available anymore";
0084         Q_ASSERT(false);
0085     }
0086 }
0087 
0088 TrackModel::~TrackModel()
0089 {
0090     if (!m_softDelete) {
0091         QScopedPointer<Mlt::Service> service(m_track->field());
0092         QScopedPointer<Mlt::Field> field(m_track->field());
0093         field->lock();
0094         while (service != nullptr && service->is_valid()) {
0095             if (service->type() == mlt_service_transition_type) {
0096                 Mlt::Transition t(mlt_transition(service->get_service()));
0097                 service.reset(service->producer());
0098                 // remove all compositing
0099                 field->disconnect_service(t);
0100                 t.disconnect_all_producers();
0101             } else {
0102                 service.reset(service->producer());
0103             }
0104         }
0105         field->unlock();
0106         m_sameCompositions.clear();
0107         m_allClips.clear();
0108         m_allCompositions.clear();
0109         m_track->remove_track(1);
0110         m_track->remove_track(0);
0111     }
0112 }
0113 
0114 int TrackModel::construct(const std::weak_ptr<TimelineModel> &parent, int id, int pos, const QString &trackName, bool audioTrack, bool singleOperation)
0115 {
0116     std::shared_ptr<TrackModel> track(new TrackModel(parent, id, trackName, audioTrack));
0117     TRACE_CONSTR(track.get(), parent, id, pos, trackName, audioTrack);
0118     id = track->m_id;
0119     if (auto ptr = parent.lock()) {
0120         ptr->registerTrack(std::move(track), pos, true, singleOperation);
0121     } else {
0122         qDebug() << "Error : construction of track failed because parent timeline is not available anymore";
0123         Q_ASSERT(false);
0124     }
0125     return id;
0126 }
0127 
0128 int TrackModel::getClipsCount()
0129 {
0130     READ_LOCK();
0131 #ifdef QT_DEBUG
0132     int count = 0;
0133     for (auto &m_playlist : m_playlists) {
0134         for (int i = 0; i < m_playlist.count(); i++) {
0135             if (!m_playlist.is_blank(i)) {
0136                 count++;
0137             }
0138         }
0139     }
0140     Q_ASSERT(count == static_cast<int>(m_allClips.size()));
0141 #else
0142     int count = int(m_allClips.size());
0143 #endif
0144     return count;
0145 }
0146 
0147 bool TrackModel::switchPlaylist(int clipId, int position, int sourcePlaylist, int destPlaylist)
0148 {
0149     QWriteLocker locker(&m_lock);
0150     if (sourcePlaylist == destPlaylist) {
0151         return true;
0152     }
0153     Q_ASSERT(!m_playlists[sourcePlaylist].is_blank_at(position) && m_playlists[destPlaylist].is_blank_at(position));
0154     int target_clip = m_playlists[sourcePlaylist].get_clip_index_at(position);
0155     std::unique_ptr<Mlt::Producer> prod(m_playlists[sourcePlaylist].replace_with_blank(target_clip));
0156     m_playlists[sourcePlaylist].consolidate_blanks();
0157     if (auto ptr = m_parent.lock()) {
0158         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(clipId);
0159         clip->setSubPlaylistIndex(destPlaylist, m_id);
0160         int index = m_playlists[destPlaylist].insert_at(position, *clip, 1);
0161         m_playlists[destPlaylist].consolidate_blanks();
0162         return index != -1;
0163     }
0164     return false;
0165 }
0166 
0167 Fun TrackModel::requestClipInsertion_lambda(int clipId, int position, bool updateView, bool finalMove, bool groupMove, const QList<int> &allowedClipMixes)
0168 {
0169     QWriteLocker locker(&m_lock);
0170     // By default, insertion occurs in topmost track
0171     int target_playlist = 0;
0172     int length = -1;
0173     if (auto ptr = m_parent.lock()) {
0174         Q_ASSERT(ptr->getClipPtr(clipId)->getCurrentTrackId() == -1);
0175         target_playlist = ptr->getClipPtr(clipId)->getSubPlaylistIndex();
0176         length = ptr->getClipPtr(clipId)->getPlaytime() - 1;
0177         /*if (target_playlist == 1 && ptr->getClipPtr(clipId)->getMixDuration() == 0) {
0178             target_playlist = 0;
0179         }*/
0180         // qDebug()<<"==== GOT TRARGET PLAYLIST: "<<target_playlist;
0181     } else {
0182         qDebug() << "impossible to get parent timeline";
0183         Q_ASSERT(false);
0184     }
0185     // Find out the clip id at position
0186     int target_clip = m_playlists[target_playlist].get_clip_index_at(position);
0187     int count = m_playlists[target_playlist].count();
0188 
0189     // we create the function that has to be executed after the melt order. This is essentially book-keeping
0190     auto end_function = [clipId, this, position, updateView, finalMove](int subPlaylist) {
0191         if (auto ptr = m_parent.lock()) {
0192             std::shared_ptr<ClipModel> clip = ptr->getClipPtr(clipId);
0193             m_allClips[clip->getId()] = clip; // store clip
0194             // update clip position and track
0195             clip->setPosition(position);
0196             if (finalMove) {
0197                 clip->setSubPlaylistIndex(subPlaylist, m_id);
0198             }
0199             int new_in = clip->getPosition();
0200             int new_out = new_in + clip->getPlaytime();
0201             ptr->m_snaps->addPoint(new_in);
0202             ptr->m_snaps->addPoint(new_out);
0203             if (updateView) {
0204                 int clip_index = getRowfromClip(clipId);
0205                 ptr->_beginInsertRows(ptr->makeTrackIndexFromID(m_id), clip_index, clip_index);
0206                 ptr->_endInsertRows();
0207                 bool audioOnly = clip->isAudioOnly();
0208                 if (!audioOnly && !isHidden() && !isAudioTrack()) {
0209                     // only refresh monitor if not an audio track and not hidden
0210                     ptr->checkRefresh(new_in, new_out);
0211                 }
0212                 if (!audioOnly && finalMove && !isAudioTrack()) {
0213                     Q_EMIT ptr->invalidateZone(new_in, new_out);
0214                 }
0215             }
0216             return true;
0217         }
0218         qDebug() << "Error : Clip Insertion failed because timeline is not available anymore";
0219         return false;
0220     };
0221     if (!finalMove && !hasMix(clipId)) {
0222         if (allowedClipMixes.isEmpty()) {
0223             if (!m_playlists[0].is_blank_at(position) || !m_playlists[1].is_blank_at(position)) {
0224                 // Track is not empty
0225                 qWarning() << "clip insert failed - non blank 1";
0226                 return []() { return false; };
0227             }
0228         } else {
0229             // This is a group move with a mix, some clips are allowed
0230             if (!m_playlists[target_playlist].is_blank_at(position)) {
0231                 // Track is not empty
0232                 qWarning() << "clip insert failed - non blank 2";
0233                 return []() { return false; };
0234             }
0235             // Check if there are clips on the other playlist, and if they are in the allowed list
0236             std::unordered_set<int> collisions = getClipsInRange(position, position + length);
0237             qDebug() << "==== DETECTING COLLISIONS AT: " << position << " to " << (position + length) << " COUNT: " << collisions.size();
0238             for (int c : collisions) {
0239                 if (!allowedClipMixes.contains(c)) {
0240                     // Track is not empty
0241                     qWarning() << "clip insert failed - non blank 3";
0242                     return []() { return false; };
0243                 }
0244             }
0245         }
0246     }
0247     if (target_clip >= count && m_playlists[target_playlist].is_blank_at(position)) {
0248         // In that case, we append after, in the first playlist
0249         return [this, position, clipId, end_function, finalMove, groupMove, target_playlist]() {
0250             if (isLocked()) {
0251                 qWarning() << "clip insert failed - locked track";
0252                 return false;
0253             }
0254             if (auto ptr = m_parent.lock()) {
0255                 // Lock MLT playlist so that we don't end up with an invalid frame being displayed
0256                 std::unique_ptr<Mlt::Field> field(m_track->field());
0257                 field->block();
0258                 m_playlists[target_playlist].lock();
0259                 std::shared_ptr<ClipModel> clip = ptr->getClipPtr(clipId);
0260                 clip->setCurrentTrackId(m_id, finalMove);
0261                 int index = m_playlists[target_playlist].insert_at(position, *clip, 1);
0262                 m_playlists[target_playlist].consolidate_blanks();
0263                 m_playlists[target_playlist].unlock();
0264                 field->unblock();
0265                 if (finalMove && !groupMove) {
0266                     ptr->updateDuration();
0267                 }
0268                 return index != -1 && end_function(target_playlist);
0269             }
0270             qDebug() << "Error : Clip Insertion failed because timeline is not available anymore";
0271             return false;
0272         };
0273     }
0274     if (m_playlists[target_playlist].is_blank_at(position)) {
0275         int blank_end = getBlankEnd(position, target_playlist);
0276         if (blank_end >= position + length) {
0277             return [this, position, clipId, end_function, target_playlist]() {
0278                 if (isLocked()) return false;
0279                 if (auto ptr = m_parent.lock()) {
0280                     // Lock MLT playlist so that we don't end up with an invalid frame being displayed
0281                     std::unique_ptr<Mlt::Field> field(m_track->field());
0282                     field->block();
0283                     m_playlists[target_playlist].lock();
0284                     std::shared_ptr<ClipModel> clip = ptr->getClipPtr(clipId);
0285                     clip->setCurrentTrackId(m_id);
0286                     int index = m_playlists[target_playlist].insert_at(position, *clip, 1);
0287                     m_playlists[target_playlist].consolidate_blanks();
0288                     m_playlists[target_playlist].unlock();
0289                     field->unblock();
0290                     return index != -1 && end_function(target_playlist);
0291                 }
0292                 qDebug() << "Error : Clip Insertion failed because timeline is not available anymore";
0293                 return false;
0294             };
0295         }
0296     }
0297     return []() { return false; };
0298 }
0299 
0300 bool TrackModel::requestClipInsertion(int clipId, int position, bool updateView, bool finalMove, Fun &undo, Fun &redo, bool groupMove, bool newInsertion,
0301                                       const QList<int> &allowedClipMixes)
0302 {
0303     QWriteLocker locker(&m_lock);
0304     if (isLocked()) {
0305         qDebug() << "==== ERROR INSERT OK LOCKED TK";
0306         return false;
0307     }
0308     if (position < 0) {
0309         qDebug() << "==== ERROR INSERT ON NEGATIVE POS: " << position;
0310         return false;
0311     }
0312     if (auto ptr = m_parent.lock()) {
0313         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(clipId);
0314         if (isAudioTrack() && !clip->canBeAudio()) {
0315             qDebug() << "// ATTEMPTING TO INSERT NON AUDIO CLIP ON AUDIO TRACK";
0316             return false;
0317         }
0318         if (!isAudioTrack() && !clip->canBeVideo()) {
0319             qDebug() << "// ATTEMPTING TO INSERT NON VIDEO CLIP ON VIDEO TRACK";
0320             return false;
0321         }
0322         Fun local_undo = []() { return true; };
0323         Fun local_redo = []() { return true; };
0324         bool res = true;
0325         if (clip->clipState() != PlaylistState::Disabled) {
0326             res = clip->setClipState(isAudioTrack() ? PlaylistState::AudioOnly : PlaylistState::VideoOnly, local_undo, local_redo);
0327         }
0328         int duration = trackDuration();
0329         auto operation = requestClipInsertion_lambda(clipId, position, updateView, finalMove, groupMove, allowedClipMixes);
0330         res = res && operation();
0331         if (res) {
0332             if (finalMove && duration != trackDuration()) {
0333                 // A clip move changed the track duration, update track effects
0334                 m_effectStack->adjustStackLength(true, 0, duration, 0, trackDuration(), 0, undo, redo, true);
0335             }
0336             auto reverse = requestClipDeletion_lambda(clipId, updateView, finalMove, groupMove, newInsertion && finalMove);
0337             UPDATE_UNDO_REDO(operation, reverse, local_undo, local_redo);
0338             UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0339             return true;
0340         }
0341         bool undone = local_undo();
0342         Q_ASSERT(undone);
0343         return false;
0344     }
0345     return false;
0346 }
0347 
0348 void TrackModel::adjustStackLength(int duration, int newDuration, Fun &undo, Fun &redo)
0349 {
0350     m_effectStack->adjustStackLength(true, 0, duration, 0, newDuration, 0, undo, redo, true);
0351 }
0352 
0353 void TrackModel::temporaryUnplugClip(int clipId)
0354 {
0355     QWriteLocker locker(&m_lock);
0356     int clip_position = m_allClips[clipId]->getPosition();
0357     int target_track = m_allClips[clipId]->getSubPlaylistIndex();
0358     auto clip_loc = getClipIndexAt(clip_position, target_track);
0359     int target_clip = clip_loc.second;
0360     // lock MLT playlist so that we don't end up with invalid frames in monitor
0361     m_playlists[target_track].lock();
0362     Q_ASSERT(target_clip < m_playlists[target_track].count());
0363     Q_ASSERT(!m_playlists[target_track].is_blank(target_clip));
0364     std::unique_ptr<Mlt::Producer> prod(m_playlists[target_track].replace_with_blank(target_clip));
0365     m_playlists[target_track].unlock();
0366 }
0367 
0368 void TrackModel::temporaryReplugClip(int cid)
0369 {
0370     QWriteLocker locker(&m_lock);
0371     int clip_position = m_allClips[cid]->getPosition();
0372     int target_track = m_allClips[cid]->getSubPlaylistIndex();
0373     m_playlists[target_track].lock();
0374     if (auto ptr = m_parent.lock()) {
0375         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(cid);
0376         m_playlists[target_track].insert_at(clip_position, *clip, 1);
0377     }
0378     m_playlists[target_track].unlock();
0379 }
0380 
0381 void TrackModel::replugClip(int clipId)
0382 {
0383     QWriteLocker locker(&m_lock);
0384     int clip_position = m_allClips[clipId]->getPosition();
0385     auto clip_loc = getClipIndexAt(clip_position, m_allClips[clipId]->getSubPlaylistIndex());
0386     int target_track = clip_loc.first;
0387     int target_clip = clip_loc.second;
0388     // lock MLT playlist so that we don't end up with invalid frames in monitor
0389     m_playlists[target_track].lock();
0390     Q_ASSERT(target_clip < m_playlists[target_track].count());
0391     Q_ASSERT(!m_playlists[target_track].is_blank(target_clip));
0392     std::unique_ptr<Mlt::Producer> prod(m_playlists[target_track].replace_with_blank(target_clip));
0393     if (auto ptr = m_parent.lock()) {
0394         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(clipId);
0395         m_playlists[target_track].insert_at(clip_position, *clip, 1);
0396         if (!clip->isAudioOnly() && !isAudioTrack()) {
0397             Q_EMIT ptr->invalidateZone(clip->getIn(), clip->getOut());
0398         }
0399         if (!clip->isAudioOnly() && !isHidden() && !isAudioTrack()) {
0400             // only refresh monitor if not an audio track and not hidden
0401             ptr->checkRefresh(clip->getIn(), clip->getOut());
0402         }
0403     }
0404     m_playlists[target_track].consolidate_blanks();
0405     m_playlists[target_track].unlock();
0406 }
0407 
0408 Fun TrackModel::requestClipDeletion_lambda(int clipId, bool updateView, bool finalMove, bool groupMove, bool finalDeletion)
0409 {
0410     QWriteLocker locker(&m_lock);
0411     // Find index of clip
0412     int clip_position = m_allClips[clipId]->getPosition();
0413     bool audioOnly = m_allClips[clipId]->isAudioOnly();
0414     int old_in = clip_position;
0415     int old_out = old_in + m_allClips[clipId]->getPlaytime();
0416     return [clip_position, clipId, old_in, old_out, updateView, audioOnly, finalMove, groupMove, finalDeletion, this]() {
0417         if (isLocked()) return false;
0418         if (finalDeletion && m_allClips[clipId]->selected) {
0419             m_allClips[clipId]->selected = false;
0420             if (auto ptr = m_parent.lock()) {
0421                 // item was selected, unselect
0422                 ptr->requestClearSelection(true);
0423             }
0424         }
0425         int target_track = m_allClips[clipId]->getSubPlaylistIndex();
0426         auto clip_loc = getClipIndexAt(clip_position, target_track);
0427         if (updateView) {
0428             int old_clip_index = getRowfromClip(clipId);
0429             auto ptr = m_parent.lock();
0430             ptr->_beginRemoveRows(ptr->makeTrackIndexFromID(getId()), old_clip_index, old_clip_index);
0431             ptr->_endRemoveRows();
0432         }
0433         int target_clip = clip_loc.second;
0434         // lock MLT playlist so that we don't end up with invalid frames in monitor
0435         m_playlists[target_track].lock();
0436         std::unique_ptr<Mlt::Field> field(m_track->field());
0437         field->block();
0438         Q_ASSERT(target_clip < m_playlists[target_track].count());
0439         Q_ASSERT(!m_playlists[target_track].is_blank(target_clip));
0440         auto prod = m_playlists[target_track].replace_with_blank(target_clip);
0441         if (prod != nullptr) {
0442             m_playlists[target_track].consolidate_blanks();
0443             m_allClips[clipId]->setCurrentTrackId(-1);
0444             // m_allClips[clipId]->setSubPlaylistIndex(-1);
0445             m_allClips.erase(clipId);
0446             delete prod;
0447             field->unblock();
0448             m_playlists[target_track].unlock();
0449             if (auto ptr = m_parent.lock()) {
0450                 ptr->m_snaps->removePoint(old_in);
0451                 ptr->m_snaps->removePoint(old_out);
0452                 if (finalMove && !ptr->m_closing) {
0453                     if (!audioOnly && !isAudioTrack()) {
0454                         Q_EMIT ptr->invalidateZone(old_in, old_out);
0455                     }
0456                     if (!groupMove && target_clip >= m_playlists[target_track].count()) {
0457                         // deleted last clip in playlist
0458                         ptr->updateDuration();
0459                     }
0460                 }
0461                 if (!audioOnly && !isHidden() && !isAudioTrack()) {
0462                     // only refresh monitor if not an audio track and not hidden
0463                     ptr->checkRefresh(old_in, old_out);
0464                 }
0465             }
0466             return true;
0467         }
0468         field->unblock();
0469         m_playlists[target_track].unlock();
0470         return false;
0471     };
0472 }
0473 
0474 bool TrackModel::requestClipDeletion(int clipId, bool updateView, bool finalMove, Fun &undo, Fun &redo, bool groupMove, bool finalDeletion,
0475                                      const QList<int> &allowedClipMixes)
0476 {
0477     QWriteLocker locker(&m_lock);
0478     Q_ASSERT(m_allClips.count(clipId) > 0);
0479     if (isLocked()) {
0480         return false;
0481     }
0482     auto old_clip = m_allClips[clipId];
0483     bool closing = false;
0484     QUuid timelineUuid;
0485     if (auto ptr = m_parent.lock()) {
0486         closing = ptr->m_closing;
0487         timelineUuid = ptr->uuid();
0488     }
0489     int old_position = old_clip->getPosition();
0490     // qDebug() << "/// REQUESTOING CLIP DELETION_: " << updateView;
0491     int duration = trackDuration();
0492     if (finalDeletion) {
0493         pCore->taskManager.discardJobs(ObjectId(KdenliveObjectType::TimelineClip, clipId, timelineUuid));
0494     }
0495     auto operation = requestClipDeletion_lambda(clipId, updateView, finalMove, groupMove, finalDeletion);
0496     if (operation()) {
0497         if (!closing && finalMove && duration != trackDuration()) {
0498             // A clip move changed the track duration, update track effects
0499             m_effectStack->adjustStackLength(true, 0, duration, 0, trackDuration(), 0, undo, redo, true);
0500         }
0501         auto reverse = requestClipInsertion_lambda(clipId, old_position, updateView, finalMove, groupMove, allowedClipMixes);
0502         UPDATE_UNDO_REDO(operation, reverse, undo, redo);
0503         return true;
0504     }
0505     return false;
0506 }
0507 
0508 int TrackModel::getBlankSizeAtPos(int frame)
0509 {
0510     READ_LOCK();
0511     int min_length = 0;
0512     int blank_length = 0;
0513     for (auto &m_playlist : m_playlists) {
0514         int playlistLength = m_playlist.get_length();
0515         if (frame >= playlistLength) {
0516             blank_length = frame - playlistLength + 1;
0517         } else {
0518             int ix = m_playlist.get_clip_index_at(frame);
0519             if (m_playlist.is_blank(ix)) {
0520                 blank_length = m_playlist.clip_length(ix);
0521             } else {
0522                 // There is a clip at that position, abort
0523                 return 0;
0524             }
0525         }
0526         if (min_length == 0 || blank_length < min_length) {
0527             min_length = blank_length;
0528         }
0529     }
0530     if (blank_length == 0) {
0531         // playlists are shorter than frame
0532         return -1;
0533     }
0534     return min_length;
0535 }
0536 
0537 int TrackModel::suggestCompositionLength(int position)
0538 {
0539     READ_LOCK();
0540     if (m_playlists[0].is_blank_at(position) && m_playlists[1].is_blank_at(position)) {
0541         return -1;
0542     }
0543     auto clip_loc = getClipIndexAt(position);
0544     int track = clip_loc.first;
0545     int index = clip_loc.second;
0546     int other_index; // index in the other track
0547     int other_track = 1 - track;
0548     int end_pos = m_playlists[track].clip_start(index) + m_playlists[track].clip_length(index);
0549     other_index = m_playlists[other_track].get_clip_index_at(end_pos);
0550     if (other_index < m_playlists[other_track].count()) {
0551         end_pos = std::min(end_pos, m_playlists[other_track].clip_start(other_index) + m_playlists[other_track].clip_length(other_index));
0552     }
0553     return end_pos - position;
0554 }
0555 
0556 QPair<int, int> TrackModel::validateCompositionLength(int pos, int offset, int duration, int endPos)
0557 {
0558     int startPos = pos;
0559     bool startingFromOffset = false;
0560     if (duration < offset) {
0561         startPos += offset;
0562         startingFromOffset = true;
0563         if (startPos + duration > endPos) {
0564             startPos = endPos - duration;
0565         }
0566     }
0567 
0568     int compsitionEnd = startPos + duration;
0569     std::unordered_set<int> existing;
0570     if (startingFromOffset) {
0571         existing = getCompositionsInRange(startPos, compsitionEnd);
0572         for (int id : existing) {
0573             if (m_allCompositions[id]->getPosition() < startPos) {
0574                 int end = m_allCompositions[id]->getPosition() + m_allCompositions[id]->getPlaytime();
0575                 startPos = qMax(startPos, end);
0576             }
0577         }
0578     } else if (offset > 0) {
0579         existing = getCompositionsInRange(startPos, startPos + offset);
0580         for (int id : existing) {
0581             int end = m_allCompositions[id]->getPosition() + m_allCompositions[id]->getPlaytime();
0582             startPos = qMax(startPos, end);
0583         }
0584     }
0585     existing = getCompositionsInRange(startPos, compsitionEnd);
0586     for (int id : existing) {
0587         int start = m_allCompositions[id]->getPosition();
0588         compsitionEnd = qMin(compsitionEnd, start);
0589     }
0590     return {startPos, compsitionEnd - startPos};
0591 }
0592 
0593 int TrackModel::getBlankSizeNearClip(int clipId, bool after)
0594 {
0595     READ_LOCK();
0596     Q_ASSERT(m_allClips.count(clipId) > 0);
0597     int clip_position = m_allClips[clipId]->getPosition();
0598     auto clip_loc = getClipIndexAt(clip_position);
0599     int track = clip_loc.first;
0600     int index = clip_loc.second;
0601     int other_index; // index in the other track
0602     int other_track = 1 - track;
0603     if (after) {
0604         int first_pos = m_playlists[track].clip_start(index) + m_playlists[track].clip_length(index);
0605         other_index = m_playlists[other_track].get_clip_index_at(first_pos);
0606         index++;
0607     } else {
0608         int last_pos = m_playlists[track].clip_start(index) - 1;
0609         other_index = m_playlists[other_track].get_clip_index_at(last_pos);
0610         index--;
0611     }
0612     if (index < 0) return 0;
0613     int length = INT_MAX;
0614     if (index < m_playlists[track].count()) {
0615         if (!m_playlists[track].is_blank(index)) {
0616             return 0;
0617         }
0618         length = std::min(length, m_playlists[track].clip_length(index));
0619     } else if (!after) {
0620         length = std::min(length, m_playlists[track].clip_start(clip_loc.second) - m_playlists[track].get_length());
0621     }
0622     if (other_index < m_playlists[other_track].count()) {
0623         if (!m_playlists[other_track].is_blank(other_index)) {
0624             return 0;
0625         }
0626         length = std::min(length, m_playlists[other_track].clip_length(other_index));
0627     } else if (!after) {
0628         length = std::min(length, m_playlists[track].clip_start(clip_loc.second) - m_playlists[other_track].get_length());
0629     }
0630     return length;
0631 }
0632 
0633 int TrackModel::getBlankSizeNearComposition(int compoId, bool after)
0634 {
0635     READ_LOCK();
0636     Q_ASSERT(m_allCompositions.count(compoId) > 0);
0637     int clip_position = m_allCompositions[compoId]->getPosition();
0638     Q_ASSERT(m_compoPos.count(clip_position) > 0);
0639     Q_ASSERT(m_compoPos[clip_position] == compoId);
0640     auto it = m_compoPos.find(clip_position);
0641     int clip_length = m_allCompositions[compoId]->getPlaytime();
0642     int length = INT_MAX;
0643     if (after) {
0644         ++it;
0645         if (it != m_compoPos.end()) {
0646             return it->first - clip_position - clip_length;
0647         }
0648     } else {
0649         if (it != m_compoPos.begin()) {
0650             --it;
0651             return clip_position - it->first - m_allCompositions[it->second]->getPlaytime();
0652         }
0653         return clip_position;
0654     }
0655     return length;
0656 }
0657 
0658 Fun TrackModel::requestClipResize_lambda(int clipId, int in, int out, bool right, bool hasMix, bool finalMove)
0659 {
0660     QWriteLocker locker(&m_lock);
0661     int clip_position = m_allClips[clipId]->getPosition();
0662     int old_in = clip_position;
0663     int old_out = old_in + m_allClips[clipId]->getPlaytime();
0664     auto clip_loc = getClipIndexAt(clip_position, m_allClips[clipId]->getSubPlaylistIndex());
0665     int target_track = clip_loc.first;
0666     int target_clip = clip_loc.second;
0667     Q_ASSERT(target_clip < m_playlists[target_track].count());
0668     int size = out - in + 1;
0669     bool checkRefresh = false;
0670     if (!isHidden() && !isAudioTrack()) {
0671         checkRefresh = true;
0672     }
0673     auto update_snaps = [old_in, old_out, checkRefresh, right, this](int new_in, int new_out) {
0674         if (auto ptr = m_parent.lock()) {
0675             if (right) {
0676                 ptr->m_snaps->removePoint(old_out);
0677                 ptr->m_snaps->addPoint(new_out);
0678             } else {
0679                 ptr->m_snaps->removePoint(old_in);
0680                 ptr->m_snaps->addPoint(new_in);
0681             }
0682             if (checkRefresh) {
0683                 if (right) {
0684                     if (old_out < new_out) {
0685                         ptr->checkRefresh(old_out, new_out);
0686                     } else {
0687                         ptr->checkRefresh(new_out, old_out);
0688                     }
0689                 } else if (old_in < new_in) {
0690                     ptr->checkRefresh(old_in, new_in);
0691                 } else {
0692                     ptr->checkRefresh(new_in, old_in);
0693                 }
0694             }
0695         } else {
0696             qDebug() << "Error : clip resize failed because parent timeline is not available anymore";
0697             Q_ASSERT(false);
0698         }
0699     };
0700 
0701     int delta = m_allClips[clipId]->getPlaytime() - size;
0702     if (delta == 0) {
0703         return []() { return true; };
0704     }
0705     if (delta > 0) { // we shrink clip
0706         return [right, target_clip, target_track, clip_position, delta, in, out, clipId, update_snaps, finalMove, this]() {
0707             if (isLocked()) return false;
0708             int target_clip_mutable = target_clip;
0709             int blank_index = right ? (target_clip_mutable + 1) : target_clip_mutable;
0710             // insert blank to space that is going to be empty
0711             m_playlists[target_track].lock();
0712             // The second is parameter is delta - 1 because this function expects an out time, which is basically size - 1
0713             m_playlists[target_track].insert_blank(blank_index, delta - 1);
0714             if (!right) {
0715                 m_allClips[clipId]->setPosition(clip_position + delta);
0716                 // Because we inserted blank before, the index of our clip has increased
0717                 target_clip_mutable++;
0718             }
0719             int err = m_playlists[target_track].resize_clip(target_clip_mutable, in, out);
0720             // make sure to do this after, to avoid messing the indexes
0721             m_playlists[target_track].consolidate_blanks();
0722             m_playlists[target_track].unlock();
0723             if (err == 0) {
0724                 update_snaps(m_allClips[clipId]->getPosition(), m_allClips[clipId]->getPosition() + out - in + 1);
0725                 if (right && finalMove && m_playlists[target_track].count() - 1 == target_clip_mutable) {
0726                     // deleted last clip in playlist
0727                     if (auto ptr = m_parent.lock()) {
0728                         ptr->updateDuration();
0729                     }
0730                 }
0731             }
0732             return err == 0;
0733         };
0734     }
0735     int blank = -1;
0736     int startPos = clip_position;
0737     if (hasMix) {
0738         startPos += m_allClips[clipId]->getMixDuration();
0739     }
0740     int endPos = m_allClips[clipId]->getPosition() + (out - in);
0741     int other_blank_end = getBlankEnd(startPos, 1 - target_track);
0742     if (right) {
0743         if (target_clip == m_playlists[target_track].count() - 1 && (hasMix || other_blank_end >= endPos)) {
0744             // clip is last, it can always be extended
0745             if (hasMix && other_blank_end < endPos && !hasEndMix(clipId)) {
0746                 // If clip has a start mix only, limit to next clip on other track
0747                 return []() { return false; };
0748             }
0749             return [this, target_clip, target_track, in, out, update_snaps, clipId, finalMove]() {
0750                 if (isLocked()) return false;
0751                 // color, image and title clips can have unlimited resize
0752                 QScopedPointer<Mlt::Producer> clip(m_playlists[target_track].get_clip(target_clip));
0753                 if (out >= clip->get_length()) {
0754                     clip->parent().set("length", out + 1);
0755                     clip->parent().set("out", out);
0756                     clip->set("length", out + 1);
0757                 }
0758                 int err = m_playlists[target_track].resize_clip(target_clip, in, out);
0759                 if (err == 0) {
0760                     update_snaps(m_allClips[clipId]->getPosition(), m_allClips[clipId]->getPosition() + out - in + 1);
0761                 }
0762                 m_playlists[target_track].consolidate_blanks();
0763                 if (finalMove && m_playlists[target_track].count() - 1 == target_clip) {
0764                     // Resized last clip in playlist
0765                     if (auto ptr = m_parent.lock()) {
0766                         ptr->updateDuration();
0767                     }
0768                 }
0769                 return err == 0;
0770             };
0771         } else {
0772         }
0773         blank = target_clip + 1;
0774     } else {
0775         if (target_clip == 0) {
0776             // clip is first, it can never be extended on the left
0777             return []() { return false; };
0778         }
0779         blank = target_clip - 1;
0780     }
0781     if (m_playlists[target_track].is_blank(blank)) {
0782         int blank_length = m_playlists[target_track].clip_length(blank);
0783         if (blank_length + delta >= 0 && (hasMix || other_blank_end >= out - in)) {
0784             return [blank_length, blank, right, clipId, delta, update_snaps, this, in, out, target_clip, target_track]() {
0785                 if (isLocked()) return false;
0786                 int target_clip_mutable = target_clip;
0787                 int err = 0;
0788                 m_playlists[target_track].lock();
0789                 if (blank_length + delta == 0) {
0790                     err = m_playlists[target_track].remove(blank);
0791                     if (!right) {
0792                         target_clip_mutable--;
0793                     }
0794                 } else {
0795                     err = m_playlists[target_track].resize_clip(blank, 0, blank_length + delta - 1);
0796                 }
0797                 if (err == 0) {
0798                     // m_track->block();
0799                     QScopedPointer<Mlt::Producer> clip(m_playlists[target_track].get_clip(target_clip_mutable));
0800                     if (out >= clip->get_length()) {
0801                         clip->parent().set("length", out + 1);
0802                         clip->parent().set("out", out);
0803                         clip->set("length", out + 1);
0804                         clip->set("out", out);
0805                     }
0806                     err = m_playlists[target_track].resize_clip(target_clip_mutable, in, out);
0807                     // m_track->unblock();
0808                 }
0809                 if (!right && err == 0) {
0810                     m_allClips[clipId]->setPosition(m_playlists[target_track].clip_start(target_clip_mutable));
0811                 }
0812                 if (err == 0) {
0813                     update_snaps(m_allClips[clipId]->getPosition(), m_allClips[clipId]->getPosition() + out - in + 1);
0814                 }
0815                 m_playlists[target_track].consolidate_blanks();
0816                 m_playlists[target_track].unlock();
0817                 return err == 0;
0818             };
0819         } else {
0820         }
0821     }
0822     return []() {
0823         qDebug() << "=====FULL FAILURE ";
0824         return false;
0825     };
0826 }
0827 
0828 int TrackModel::getId() const
0829 {
0830     return m_id;
0831 }
0832 
0833 int TrackModel::getClipByStartPosition(int position) const
0834 {
0835     READ_LOCK();
0836     for (auto &clip : m_allClips) {
0837         if (clip.second->getPosition() == position) {
0838             return clip.second->getId();
0839         }
0840     }
0841     return -1;
0842 }
0843 
0844 int TrackModel::getClipByPosition(int position, int playlist)
0845 {
0846     READ_LOCK();
0847     QSharedPointer<Mlt::Producer> prod(nullptr);
0848     if ((playlist == 0 || playlist == -1) && m_playlists[0].count() > 0) {
0849         prod = QSharedPointer<Mlt::Producer>(m_playlists[0].get_clip_at(position));
0850     }
0851     if (playlist != 0 && (!prod || prod->is_blank()) && m_playlists[1].count() > 0) {
0852         prod = QSharedPointer<Mlt::Producer>(m_playlists[1].get_clip_at(position));
0853     }
0854     if (!prod || prod->is_blank()) {
0855         return -1;
0856     }
0857     int cid = prod->get_int("_kdenlive_cid");
0858     if (playlist == -1) {
0859         if (hasStartMix(cid)) {
0860             if (position < m_allClips[cid]->getPosition() + m_allClips[cid]->getMixCutPosition()) {
0861                 return m_mixList.key(cid, -1);
0862             }
0863         }
0864         if (m_mixList.contains(cid)) {
0865             // Clip has end mix
0866             int otherId = m_mixList.value(cid);
0867             if (position >= m_allClips[cid]->getPosition() + m_allClips[cid]->getPlaytime() - m_allClips[otherId]->getMixCutPosition()) {
0868                 return otherId;
0869             }
0870         }
0871     }
0872     return cid;
0873 }
0874 
0875 QSharedPointer<Mlt::Producer> TrackModel::getClipProducer(int clipId)
0876 {
0877     READ_LOCK();
0878     QSharedPointer<Mlt::Producer> prod(nullptr);
0879     if (m_playlists[0].count() > 0) {
0880         prod = QSharedPointer<Mlt::Producer>(m_playlists[0].get_clip(clipId));
0881     }
0882     if ((!prod || prod->is_blank()) && m_playlists[1].count() > 0) {
0883         prod = QSharedPointer<Mlt::Producer>(m_playlists[1].get_clip(clipId));
0884     }
0885     return prod;
0886 }
0887 
0888 int TrackModel::getCompositionByPosition(int position)
0889 {
0890     READ_LOCK();
0891     for (const auto &comp : m_compoPos) {
0892         if (comp.first == position) {
0893             return comp.second;
0894         } else if (comp.first < position) {
0895             if (comp.first + m_allCompositions[comp.second]->getPlaytime() >= position) {
0896                 return comp.second;
0897             }
0898         }
0899     }
0900     return -1;
0901 }
0902 
0903 int TrackModel::getClipByRow(int row) const
0904 {
0905     READ_LOCK();
0906     if (row >= static_cast<int>(m_allClips.size())) {
0907         return -1;
0908     }
0909     auto it = m_allClips.cbegin();
0910     std::advance(it, row);
0911     return (*it).first;
0912 }
0913 
0914 std::unordered_set<int> TrackModel::getClipsInRange(int position, int end)
0915 {
0916     READ_LOCK();
0917     std::unordered_set<int> ids;
0918     for (const auto &clp : m_allClips) {
0919         int pos = clp.second->getPosition();
0920         int length = clp.second->getPlaytime();
0921         if (end > -1 && pos >= end) {
0922             continue;
0923         }
0924         if (pos >= position || pos + length - 1 >= position) {
0925             ids.insert(clp.first);
0926         }
0927     }
0928     return ids;
0929 }
0930 
0931 int TrackModel::getRowfromClip(int clipId) const
0932 {
0933     READ_LOCK();
0934     Q_ASSERT(m_allClips.count(clipId) > 0);
0935     return int(std::distance(m_allClips.begin(), m_allClips.find(clipId)));
0936 }
0937 
0938 std::unordered_set<int> TrackModel::getCompositionsInRange(int position, int end)
0939 {
0940     READ_LOCK();
0941     // TODO: this function doesn't take into accounts the fact that there are two tracks
0942     std::unordered_set<int> ids;
0943     for (const auto &compo : m_allCompositions) {
0944         int pos = compo.second->getPosition();
0945         int length = compo.second->getPlaytime();
0946         if (end > -1 && pos >= end) {
0947             continue;
0948         }
0949         if (pos >= position || pos + length - 1 >= position) {
0950 
0951             ids.insert(compo.first);
0952         }
0953     }
0954     return ids;
0955 }
0956 
0957 int TrackModel::getRowfromComposition(int tid) const
0958 {
0959     READ_LOCK();
0960     Q_ASSERT(m_allCompositions.count(tid) > 0);
0961     return int(m_allClips.size()) + int(std::distance(m_allCompositions.begin(), m_allCompositions.find(tid)));
0962 }
0963 
0964 QVariant TrackModel::getProperty(const QString &name) const
0965 {
0966     READ_LOCK();
0967     return QString(m_track->get(name.toUtf8().constData()));
0968 }
0969 
0970 void TrackModel::setProperty(const QString &name, const QString &value)
0971 {
0972     QWriteLocker locker(&m_lock);
0973     m_track->set(name.toUtf8().constData(), value.toUtf8().constData());
0974     // Hide property must be defined at playlist level or it won't be saved
0975     if (name == QLatin1String("kdenlive:audio_track") || name == QLatin1String("hide")) {
0976         for (auto &m_playlist : m_playlists) {
0977             m_playlist.set(name.toUtf8().constData(), value.toInt());
0978         }
0979     }
0980 }
0981 
0982 bool TrackModel::checkConsistency()
0983 {
0984     auto ptr = m_parent.lock();
0985     if (!ptr) {
0986         return false;
0987     }
0988     auto check_blank_zone = [&](int playlist, int in, int out) {
0989         if (in >= m_playlists[playlist].get_playtime()) {
0990             return true;
0991         }
0992         int index = m_playlists[playlist].get_clip_index_at(in);
0993         if (!m_playlists[playlist].is_blank(index)) {
0994             return false;
0995         }
0996         int cin = m_playlists[playlist].clip_start(index);
0997         if (cin > in) {
0998             return false;
0999         }
1000         if (cin + m_playlists[playlist].clip_length(index) - 1 < out) {
1001             return false;
1002         }
1003         return true;
1004     };
1005     std::vector<std::pair<int, int>> clips; // clips stored by (position, id)
1006     for (const auto &c : m_allClips) {
1007         Q_ASSERT(c.second);
1008         Q_ASSERT(c.second.get() == ptr->getClipPtr(c.first).get());
1009         clips.emplace_back(c.second->getPosition(), c.first);
1010     }
1011     std::sort(clips.begin(), clips.end());
1012     int last_out = 0;
1013     for (size_t i = 0; i < clips.size(); ++i) {
1014         auto cur_clip = m_allClips[clips[i].second];
1015         if (last_out < clips[i].first) {
1016             // we have some blank space before this clip, check it
1017             for (int pl = 0; pl <= 1; ++pl) {
1018                 if (!check_blank_zone(pl, last_out, clips[i].first - 1)) {
1019                     qDebug() << "ERROR: Some blank was required on playlist " << pl << " between " << last_out << " and " << clips[i].first - 1;
1020                     return false;
1021                 }
1022             }
1023         }
1024         int cur_playlist = cur_clip->getSubPlaylistIndex();
1025         int clip_index = m_playlists[cur_playlist].get_clip_index_at(clips[i].first);
1026         if (m_playlists[cur_playlist].is_blank(clip_index)) {
1027             qDebug() << "ERROR: Found blank when clip was required at position " << clips[i].first;
1028             return false;
1029         }
1030         if (m_playlists[cur_playlist].clip_start(clip_index) != clips[i].first) {
1031             qDebug() << "ERROR: Inconsistent start position for clip at position " << clips[i].first;
1032             return false;
1033         }
1034         if (m_playlists[cur_playlist].clip_start(clip_index) != clips[i].first) {
1035             qDebug() << "ERROR: Inconsistent start position for clip at position " << clips[i].first;
1036             return false;
1037         }
1038         if (m_playlists[cur_playlist].clip_length(clip_index) != cur_clip->getPlaytime()) {
1039             qDebug() << "ERROR: Inconsistent length for clip at position " << clips[i].first;
1040             return false;
1041         }
1042         auto pr = m_playlists[cur_playlist].get_clip(clip_index);
1043         Mlt::Producer prod(pr);
1044         if (!prod.same_clip(*cur_clip)) {
1045             qDebug() << "ERROR: Wrong clip at position " << clips[i].first;
1046             delete pr;
1047             return false;
1048         }
1049         delete pr;
1050 
1051         // the current playlist is valid, we check that the other is essentially blank
1052         int other_playlist = (cur_playlist + 1) % 2;
1053         int in_blank = clips[i].first;
1054         int out_blank = clips[i].first + cur_clip->getPlaytime() - 1;
1055 
1056         // the previous clip on the same playlist must not intersect
1057         int prev_clip_id_same_playlist = -1;
1058         for (int j = int(i) - 1; j >= 0; --j) {
1059             if (cur_playlist == m_allClips[clips[size_t(j)].second]->getSubPlaylistIndex()) {
1060                 prev_clip_id_same_playlist = j;
1061                 break;
1062             }
1063         }
1064         if (prev_clip_id_same_playlist >= 0 &&
1065             clips[size_t(prev_clip_id_same_playlist)].first + m_allClips[clips[size_t(prev_clip_id_same_playlist)].second]->getPlaytime() > clips[i].first) {
1066             qDebug() << "ERROR: found overlapping clips at position " << clips[i].first;
1067             return false;
1068         }
1069 
1070         // the previous clip on the other playlist might restrict the blank in/out
1071         int prev_clip_id_other_playlist = -1;
1072         for (int j = int(i) - 1; j >= 0; --j) {
1073             if (other_playlist == m_allClips[clips[size_t(j)].second]->getSubPlaylistIndex()) {
1074                 prev_clip_id_other_playlist = j;
1075                 break;
1076             }
1077         }
1078         if (prev_clip_id_other_playlist >= 0) {
1079             in_blank = std::max(in_blank, clips[size_t(prev_clip_id_other_playlist)].first +
1080                                               m_allClips[clips[size_t(prev_clip_id_other_playlist)].second]->getPlaytime());
1081         }
1082 
1083         // the next clip on the other playlist might restrict the blank in/out
1084         int next_clip_id_other_playlist = -1;
1085         for (size_t j = i + 1; j < clips.size(); ++j) {
1086             if (other_playlist == m_allClips[clips[j].second]->getSubPlaylistIndex()) {
1087                 next_clip_id_other_playlist = int(j);
1088                 break;
1089             }
1090         }
1091         if (next_clip_id_other_playlist >= 0) {
1092             out_blank = std::min(out_blank, clips[size_t(next_clip_id_other_playlist)].first - 1);
1093         }
1094         if (in_blank <= out_blank && !check_blank_zone(other_playlist, in_blank, out_blank)) {
1095             qDebug() << "ERROR: we expected blank on playlist " << other_playlist << " between " << in_blank << " and " << out_blank;
1096             return false;
1097         }
1098 
1099         last_out = clips[i].first + cur_clip->getPlaytime();
1100     }
1101     int playtime = std::max(m_playlists[0].get_playtime(), m_playlists[1].get_playtime());
1102     if (!clips.empty() && playtime != clips.back().first + m_allClips[clips.back().second]->getPlaytime()) {
1103         qDebug() << "Error: playtime is " << playtime << " but was expected to be" << clips.back().first + m_allClips[clips.back().second]->getPlaytime();
1104         return false;
1105     }
1106 
1107     // We now check compositions positions
1108     if (m_allCompositions.size() != m_compoPos.size()) {
1109         qDebug() << "Error: the number of compositions position doesn't match number of compositions";
1110         return false;
1111     }
1112     for (const auto &compo : m_allCompositions) {
1113         int pos = compo.second->getPosition();
1114         if (m_compoPos.count(pos) == 0) {
1115             qDebug() << "Error: the position of composition " << compo.first << " is not properly stored";
1116             return false;
1117         }
1118         if (m_compoPos[pos] != compo.first) {
1119             qDebug() << "Error: found composition" << m_compoPos[pos] << "instead of " << compo.first << "at position" << pos;
1120             return false;
1121         }
1122     }
1123     for (auto it = m_compoPos.begin(); it != m_compoPos.end(); ++it) {
1124         int compoId = it->second;
1125         int cur_in = m_allCompositions[compoId]->getPosition();
1126         Q_ASSERT(cur_in == it->first);
1127         int cur_out = cur_in + m_allCompositions[compoId]->getPlaytime() - 1;
1128         ++it;
1129         if (it != m_compoPos.end()) {
1130             int next_compoId = it->second;
1131             int next_in = m_allCompositions[next_compoId]->getPosition();
1132             int next_out = next_in + m_allCompositions[next_compoId]->getPlaytime() - 1;
1133             if (next_in <= cur_out) {
1134                 qDebug() << "Error: found collision between composition " << compoId << "[ " << cur_in << ", " << cur_out << "] and " << next_compoId << "[ "
1135                          << next_in << ", " << next_out << "]";
1136                 return false;
1137             }
1138         }
1139         --it;
1140     }
1141     // Check Mixes
1142     QScopedPointer<Mlt::Service> service(m_track->field());
1143     int mixCount = 0;
1144     while (service != nullptr && service->is_valid()) {
1145         if (service->type() == mlt_service_transition_type) {
1146             Mlt::Transition t(mlt_transition(service->get_service()));
1147             service.reset(service->producer());
1148             // Check that the mix has correct in/out
1149             int mainId = -1;
1150             int mixIn = t.get_in();
1151             for (auto &sameComposition : m_sameCompositions) {
1152                 if (static_cast<Mlt::Transition *>(sameComposition.second->getAsset())->get_in() == mixIn) {
1153                     // Found mix in list
1154                     mainId = sameComposition.first;
1155                     break;
1156                 }
1157             }
1158             if (mainId == -1) {
1159                 qDebug() << "=== Incoherent mix found at: " << mixIn;
1160                 return false;
1161             }
1162             // Check in/out)
1163             if (mixIn != m_allClips[mainId]->getPosition()) {
1164                 qDebug() << "=== Mix not aligned with its master clip: " << mainId << ", at: " << m_allClips[mainId]->getPosition() << ", MIX at: " << mixIn;
1165                 return false;
1166             }
1167             int secondClipId = m_mixList.key(mainId);
1168             if (t.get_out() != m_allClips[secondClipId]->getPosition() + m_allClips[secondClipId]->getPlaytime()) {
1169                 qDebug() << "=== Mix not aligned with its second clip: " << secondClipId
1170                          << ", end at: " << m_allClips[secondClipId]->getPosition() + m_allClips[secondClipId]->getPlaytime() << ", MIX at: " << t.get_out();
1171                 return false;
1172             }
1173             mixCount++;
1174         } else {
1175             service.reset(service->producer());
1176         }
1177     }
1178     if (mixCount != static_cast<int>(m_sameCompositions.size()) || static_cast<int>(m_sameCompositions.size()) != m_mixList.count()) {
1179         // incoherent mix count
1180         qDebug() << "=== INCORRECT mix count. Existing: " << mixCount << "; REGISTERED: " << m_mixList.count();
1181         return false;
1182     }
1183     return true;
1184 }
1185 
1186 std::pair<int, int> TrackModel::getClipIndexAt(int position, int playlist)
1187 {
1188     READ_LOCK();
1189     if (playlist == -1) {
1190         for (int j = 0; j < 2; j++) {
1191             if (!m_playlists[j].is_blank_at(position)) {
1192                 return {j, m_playlists[j].get_clip_index_at(position)};
1193             }
1194         }
1195         return {-1, -1};
1196     }
1197     if (!m_playlists[playlist].is_blank_at(position)) {
1198         return {playlist, m_playlists[playlist].get_clip_index_at(position)};
1199     }
1200     qDebug() << "=== CANNOT FIND CLIP ON PLAYLIST: " << playlist << " AT POSITION: " << position << ", TID: " << m_id;
1201     Q_ASSERT(false);
1202     return {-1, -1};
1203 }
1204 
1205 bool TrackModel::isLastClip(int position)
1206 {
1207     READ_LOCK();
1208     for (auto &m_playlist : m_playlists) {
1209         if (!m_playlist.is_blank_at(position)) {
1210             return m_playlist.get_clip_index_at(position) == m_playlist.count() - 1;
1211         }
1212     }
1213     return false;
1214 }
1215 
1216 bool TrackModel::isBlankAt(int position, int playlist)
1217 {
1218     READ_LOCK();
1219     if (playlist == -1) {
1220         return m_playlists[0].is_blank_at(position) && m_playlists[1].is_blank_at(position);
1221     }
1222     return m_playlists[playlist].is_blank_at(position);
1223 }
1224 
1225 int TrackModel::getNextBlankStart(int position)
1226 {
1227     while (!isBlankAt(position)) {
1228         int end1 = getClipEnd(position, 0);
1229         int end2 = getClipEnd(position, 1);
1230         if (end1 > position) {
1231             position = end1;
1232         } else if (end2 > position) {
1233             position = end2;
1234         } else {
1235             // We reached playlist end
1236             return -1;
1237         }
1238     }
1239     return getBlankStart(position);
1240 }
1241 
1242 int TrackModel::getBlankStart(int position)
1243 {
1244     READ_LOCK();
1245     int result = 0;
1246     for (auto &playlist : m_playlists) {
1247         if (playlist.count() == 0) {
1248             break;
1249         }
1250         if (!playlist.is_blank_at(position)) {
1251             result = position;
1252             break;
1253         }
1254         int clip_index = playlist.get_clip_index_at(position);
1255         int start = playlist.clip_start(clip_index);
1256         if (start > result) {
1257             result = start;
1258         }
1259     }
1260     return result;
1261 }
1262 
1263 int TrackModel::getClipStart(int position, int track)
1264 {
1265     if (track == -1) {
1266         return getBlankStart(position);
1267     }
1268     READ_LOCK();
1269     if (m_playlists[track].is_blank_at(position)) {
1270         return position;
1271     }
1272     int clip_index = m_playlists[track].get_clip_index_at(position);
1273     return m_playlists[track].clip_start(clip_index);
1274 }
1275 
1276 int TrackModel::getClipEnd(int position, int track)
1277 {
1278     if (track == -1) {
1279         return getBlankStart(position);
1280     }
1281     READ_LOCK();
1282     if (m_playlists[track].is_blank_at(position)) {
1283         return position;
1284     }
1285     int clip_index = m_playlists[track].get_clip_index_at(position);
1286     clip_index++;
1287     return m_playlists[track].clip_start(clip_index);
1288 }
1289 
1290 int TrackModel::getBlankStart(int position, int track)
1291 {
1292     if (track == -1) {
1293         return getBlankStart(position);
1294     }
1295     READ_LOCK();
1296     int result = 0;
1297     if (!m_playlists[track].is_blank_at(position)) {
1298         return position;
1299     }
1300     int clip_index = m_playlists[track].get_clip_index_at(position);
1301     int start = m_playlists[track].clip_start(clip_index);
1302     if (start > result) {
1303         result = start;
1304     }
1305     return result;
1306 }
1307 
1308 int TrackModel::getBlankEnd(int position, int track)
1309 {
1310     if (track == -1) {
1311         return getBlankEnd(position);
1312     }
1313     READ_LOCK();
1314     // Q_ASSERT(m_playlists[track].is_blank_at(position));
1315     if (!m_playlists[track].is_blank_at(position)) {
1316         return position;
1317     }
1318     int clip_index = m_playlists[track].get_clip_index_at(position);
1319     int count = m_playlists[track].count();
1320     if (clip_index < count) {
1321         int blank_start = m_playlists[track].clip_start(clip_index);
1322         int blank_length = m_playlists[track].clip_length(clip_index) - 1;
1323         return blank_start + blank_length;
1324     }
1325     return INT_MAX;
1326 }
1327 
1328 int TrackModel::getBlankEnd(int position)
1329 {
1330     READ_LOCK();
1331     int end = INT_MAX;
1332     for (int j = 0; j < 2; j++) {
1333         end = std::min(getBlankEnd(position, j), end);
1334     }
1335     return end;
1336 }
1337 
1338 Fun TrackModel::requestCompositionResize_lambda(int compoId, int in, int out, bool logUndo)
1339 {
1340     QWriteLocker locker(&m_lock);
1341     int compo_position = m_allCompositions[compoId]->getPosition();
1342     Q_ASSERT(m_compoPos.count(compo_position) > 0);
1343     Q_ASSERT(m_compoPos[compo_position] == compoId);
1344     int old_in = compo_position;
1345     int old_out = old_in + m_allCompositions[compoId]->getPlaytime() - 1;
1346     qDebug() << "compo resize " << compoId << in << "-" << out << " / " << old_in << "-" << old_out;
1347     if (out == -1) {
1348         out = in + old_out - old_in;
1349     }
1350 
1351     auto update_snaps = [old_in, old_out, logUndo, this](int new_in, int new_out) {
1352         if (auto ptr = m_parent.lock()) {
1353             ptr->m_snaps->removePoint(old_in);
1354             ptr->m_snaps->removePoint(old_out + 1);
1355             ptr->m_snaps->addPoint(new_in);
1356             ptr->m_snaps->addPoint(new_out);
1357             ptr->checkRefresh(old_in, old_out);
1358             ptr->checkRefresh(new_in, new_out);
1359             if (logUndo) {
1360                 Q_EMIT ptr->invalidateZone(old_in, old_out);
1361                 Q_EMIT ptr->invalidateZone(new_in, new_out);
1362             }
1363             // ptr->adjustAssetRange(compoId, new_in, new_out);
1364         } else {
1365             qDebug() << "Error : Composition resize failed because parent timeline is not available anymore";
1366             Q_ASSERT(false);
1367         }
1368     };
1369 
1370     if (in == compo_position && (out == -1 || out == old_out)) {
1371         return []() {
1372             qDebug() << "//// NO MOVE PERFORMED\n!!!!!!!!!!!!!!!!!!!!!!!!!!";
1373             return true;
1374         };
1375     }
1376 
1377     // temporary remove of current compo to check collisions
1378     qDebug() << "// CURRENT COMPOSITIONS ----\n" << m_compoPos << "\n--------------";
1379     m_compoPos.erase(compo_position);
1380     bool intersecting = hasIntersectingComposition(in, out);
1381     // put it back
1382     m_compoPos[compo_position] = compoId;
1383 
1384     if (intersecting) {
1385         return []() {
1386             qDebug() << "//// FALSE MOVE PERFORMED\n!!!!!!!!!!!!!!!!!!!!!!!!!!";
1387             return false;
1388         };
1389     }
1390 
1391     return [in, out, compoId, update_snaps, this]() {
1392         if (isLocked()) return false;
1393         m_compoPos.erase(m_allCompositions[compoId]->getPosition());
1394         m_allCompositions[compoId]->setInOut(in, out);
1395         update_snaps(in, out + 1);
1396         m_compoPos[m_allCompositions[compoId]->getPosition()] = compoId;
1397         return true;
1398     };
1399 }
1400 
1401 bool TrackModel::requestCompositionInsertion(int compoId, int position, bool updateView, bool finalMove, Fun &undo, Fun &redo)
1402 {
1403     QWriteLocker locker(&m_lock);
1404     if (isLocked()) {
1405         return false;
1406     }
1407     auto operation = requestCompositionInsertion_lambda(compoId, position, updateView, finalMove);
1408     if (operation()) {
1409         auto reverse = requestCompositionDeletion_lambda(compoId, updateView, finalMove);
1410         UPDATE_UNDO_REDO(operation, reverse, undo, redo);
1411         return true;
1412     }
1413     return false;
1414 }
1415 
1416 bool TrackModel::requestCompositionDeletion(int compoId, bool updateView, bool finalMove, Fun &undo, Fun &redo, bool finalDeletion)
1417 {
1418     QWriteLocker locker(&m_lock);
1419     if (isLocked()) {
1420         return false;
1421     }
1422     Q_ASSERT(m_allCompositions.count(compoId) > 0);
1423     auto old_composition = m_allCompositions[compoId];
1424     int old_position = old_composition->getPosition();
1425     Q_ASSERT(m_compoPos.count(old_position) > 0);
1426     Q_ASSERT(m_compoPos[old_position] == compoId);
1427     auto operation = requestCompositionDeletion_lambda(compoId, updateView, finalMove && finalDeletion);
1428     if (operation()) {
1429         auto reverse = requestCompositionInsertion_lambda(compoId, old_position, updateView, finalMove);
1430         UPDATE_UNDO_REDO(operation, reverse, undo, redo);
1431         return true;
1432     }
1433     return false;
1434 }
1435 
1436 Fun TrackModel::requestCompositionDeletion_lambda(int compoId, bool updateView, bool finalMove)
1437 {
1438     QWriteLocker locker(&m_lock);
1439     // Find index of clip
1440     int clip_position = m_allCompositions[compoId]->getPosition();
1441     int old_in = clip_position;
1442     int old_out = old_in + m_allCompositions[compoId]->getPlaytime();
1443     return [compoId, old_in, old_out, updateView, finalMove, this]() {
1444         if (isLocked()) return false;
1445         int old_clip_index = getRowfromComposition(compoId);
1446         if (finalMove && m_allCompositions[compoId]->selected) {
1447             m_allCompositions[compoId]->selected = false;
1448             if (auto ptr = m_parent.lock()) {
1449                 // item was selected, unselect
1450                 ptr->requestClearSelection(true);
1451             }
1452         }
1453         auto ptr = m_parent.lock();
1454         if (updateView) {
1455             ptr->_beginRemoveRows(ptr->makeTrackIndexFromID(getId()), old_clip_index, old_clip_index);
1456             ptr->_endRemoveRows();
1457         }
1458         m_allCompositions[compoId]->setCurrentTrackId(-1);
1459         m_allCompositions.erase(compoId);
1460         m_compoPos.erase(old_in);
1461         ptr->m_snaps->removePoint(old_in);
1462         ptr->m_snaps->removePoint(old_out);
1463         if (finalMove) {
1464             Q_EMIT ptr->invalidateZone(old_in, old_out);
1465         }
1466         return true;
1467     };
1468 }
1469 
1470 int TrackModel::getCompositionByRow(int row) const
1471 {
1472     READ_LOCK();
1473     if (row < int(m_allClips.size())) {
1474         return -1;
1475     }
1476     Q_ASSERT(row <= int(m_allClips.size() + m_allCompositions.size()));
1477     auto it = m_allCompositions.cbegin();
1478     std::advance(it, row - int(m_allClips.size()));
1479     return (*it).first;
1480 }
1481 
1482 int TrackModel::getCompositionsCount() const
1483 {
1484     READ_LOCK();
1485     return int(m_allCompositions.size());
1486 }
1487 
1488 Fun TrackModel::requestCompositionInsertion_lambda(int compoId, int position, bool updateView, bool finalMove)
1489 {
1490     QWriteLocker locker(&m_lock);
1491     bool intersecting = true;
1492     if (auto ptr = m_parent.lock()) {
1493         intersecting = hasIntersectingComposition(position, position + ptr->getCompositionPlaytime(compoId) - 1);
1494     } else {
1495         qDebug() << "Error : Composition Insertion failed because timeline is not available anymore";
1496     }
1497     if (!intersecting) {
1498         return [compoId, this, position, updateView, finalMove]() {
1499             if (isLocked()) return false;
1500             if (auto ptr = m_parent.lock()) {
1501                 std::shared_ptr<CompositionModel> composition = ptr->getCompositionPtr(compoId);
1502                 m_allCompositions[composition->getId()] = composition; // store clip
1503                 // update clip position and track
1504                 composition->setCurrentTrackId(getId());
1505                 int new_in = position;
1506                 int new_out = new_in + composition->getPlaytime();
1507                 composition->setInOut(new_in, new_out - 1);
1508                 if (updateView) {
1509                     int composition_index = getRowfromComposition(composition->getId());
1510                     ptr->_beginInsertRows(ptr->makeTrackIndexFromID(composition->getCurrentTrackId()), composition_index, composition_index);
1511                     ptr->_endInsertRows();
1512                 }
1513                 ptr->m_snaps->addPoint(new_in);
1514                 ptr->m_snaps->addPoint(new_out);
1515                 m_compoPos[new_in] = composition->getId();
1516                 if (finalMove) {
1517                     Q_EMIT ptr->invalidateZone(new_in, new_out);
1518                 }
1519                 return true;
1520             }
1521             qDebug() << "Error : Composition Insertion failed because timeline is not available anymore";
1522             return false;
1523         };
1524     }
1525     return []() { return false; };
1526 }
1527 
1528 bool TrackModel::hasIntersectingComposition(int in, int out) const
1529 {
1530     READ_LOCK();
1531     auto it = m_compoPos.lower_bound(in);
1532     if (m_compoPos.empty()) {
1533         return false;
1534     }
1535     if (it != m_compoPos.end() && it->first <= out) {
1536         // compo at it intersects
1537         return true;
1538     }
1539     if (it == m_compoPos.begin()) {
1540         return false;
1541     }
1542     --it;
1543     int end = it->first + m_allCompositions.at(it->second)->getPlaytime() - 1;
1544     return end >= in;
1545 
1546     return false;
1547 }
1548 
1549 bool TrackModel::addEffect(const QString &effectId)
1550 {
1551     READ_LOCK();
1552     return m_effectStack->appendEffect(effectId);
1553 }
1554 
1555 const QString TrackModel::effectNames() const
1556 {
1557     READ_LOCK();
1558     return m_effectStack->effectNames();
1559 }
1560 
1561 bool TrackModel::stackEnabled() const
1562 {
1563     READ_LOCK();
1564     return m_effectStack->isStackEnabled();
1565 }
1566 
1567 void TrackModel::setEffectStackEnabled(bool enable)
1568 {
1569     m_effectStack->setEffectStackEnabled(enable);
1570 }
1571 
1572 int TrackModel::trackDuration() const
1573 {
1574     return m_track->get_length();
1575 }
1576 
1577 bool TrackModel::isLocked() const
1578 {
1579     READ_LOCK();
1580     return m_track->get_int("kdenlive:locked_track");
1581 }
1582 
1583 bool TrackModel::isTimelineActive() const
1584 {
1585     READ_LOCK();
1586     return m_track->get_int("kdenlive:timeline_active");
1587 }
1588 
1589 bool TrackModel::shouldReceiveTimelineOp() const
1590 {
1591     READ_LOCK();
1592     return isTimelineActive() && !isLocked();
1593 }
1594 
1595 bool TrackModel::isAudioTrack() const
1596 {
1597     return m_track->get_int("kdenlive:audio_track") == 1;
1598 }
1599 
1600 Mlt::Tractor *TrackModel::getTrackService()
1601 {
1602     return m_track.get();
1603 }
1604 
1605 PlaylistState::ClipState TrackModel::trackType() const
1606 {
1607     return (m_track->get_int("kdenlive:audio_track") == 1 ? PlaylistState::AudioOnly : PlaylistState::VideoOnly);
1608 }
1609 
1610 bool TrackModel::isHidden() const
1611 {
1612     return m_track->get_int("hide") & 1;
1613 }
1614 
1615 bool TrackModel::isMute() const
1616 {
1617     return m_track->get_int("hide") & 2;
1618 }
1619 
1620 bool TrackModel::importEffects(std::weak_ptr<Mlt::Service> service)
1621 {
1622     QWriteLocker locker(&m_lock);
1623     m_effectStack->importEffects(std::move(service), trackType());
1624     return true;
1625 }
1626 
1627 bool TrackModel::copyEffect(const std::shared_ptr<EffectStackModel> &stackModel, int rowId)
1628 {
1629     QWriteLocker locker(&m_lock);
1630     return m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId), isAudioTrack() ? PlaylistState::AudioOnly : PlaylistState::VideoOnly);
1631 }
1632 
1633 void TrackModel::lock()
1634 {
1635     setProperty(QStringLiteral("kdenlive:locked_track"), QStringLiteral("1"));
1636     if (auto ptr = m_parent.lock()) {
1637         QModelIndex ix = ptr->makeTrackIndexFromID(m_id);
1638         Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::IsLockedRole});
1639     }
1640 }
1641 void TrackModel::unlock()
1642 {
1643     setProperty(QStringLiteral("kdenlive:locked_track"), nullptr);
1644     if (auto ptr = m_parent.lock()) {
1645         QModelIndex ix = ptr->makeTrackIndexFromID(m_id);
1646         Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::IsLockedRole});
1647     }
1648 }
1649 
1650 bool TrackModel::isAvailable(int position, int duration, int playlist)
1651 {
1652     if (playlist == -1) {
1653         // Check on both playlists
1654         for (auto &pl : m_playlists) {
1655             int start_clip = pl.get_clip_index_at(position);
1656             int end_clip = pl.get_clip_index_at(position + duration - 1);
1657             if (start_clip != end_clip || !pl.is_blank(start_clip)) {
1658                 return false;
1659             }
1660         }
1661         return true;
1662     }
1663     int start_clip = m_playlists[playlist].get_clip_index_at(position);
1664     int end_clip = m_playlists[playlist].get_clip_index_at(position + duration - 1);
1665     if (start_clip != end_clip) {
1666         return false;
1667     }
1668     return m_playlists[playlist].is_blank(start_clip);
1669 }
1670 
1671 bool TrackModel::isAvailableWithExceptions(int position, int duration, const QVector<int> &exceptions)
1672 {
1673     // Check on both playlists
1674     QSharedPointer<Mlt::Producer> prod = nullptr;
1675     for (auto &playlist : m_playlists) {
1676         int start_clip = playlist.get_clip_index_at(position);
1677         int end_clip = playlist.get_clip_index_at(position + duration - 1);
1678         for (int ix = start_clip; ix <= end_clip; ix++) {
1679             if (playlist.is_blank(ix)) {
1680                 continue;
1681             }
1682             prod.reset(playlist.get_clip(ix));
1683             if (prod) {
1684                 if (!exceptions.contains(prod->get_int("_kdenlive_cid"))) {
1685                     return false;
1686                 }
1687             }
1688         }
1689     }
1690     return true;
1691 }
1692 
1693 bool TrackModel::requestRemoveMix(std::pair<int, int> clipIds, Fun &undo, Fun &redo)
1694 {
1695     int mixDuration;
1696     int mixCutPos;
1697     int endPos;
1698     int firstInPos;
1699     int secondInPos;
1700     int mixPosition;
1701     int src_track = 0;
1702     int first_src_track = 0;
1703     bool secondClipHasEndMix = false;
1704     bool firstClipHasStartMix = false;
1705     QList<int> allowedMixes = {clipIds.first};
1706     if (auto ptr = m_parent.lock()) {
1707         // The clip that will be moved to playlist 1
1708         std::shared_ptr<ClipModel> firstClip(ptr->getClipPtr(clipIds.first));
1709         std::shared_ptr<ClipModel> secondClip(ptr->getClipPtr(clipIds.second));
1710         mixDuration = secondClip->getMixDuration();
1711         mixCutPos = secondClip->getMixCutPosition();
1712         mixPosition = secondClip->getPosition();
1713         firstInPos = firstClip->getPosition();
1714         secondInPos = mixPosition + mixDuration - mixCutPos;
1715         endPos = mixPosition + secondClip->getPlaytime();
1716         secondClipHasEndMix = hasEndMix(clipIds.second);
1717         firstClipHasStartMix = hasStartMix(clipIds.first);
1718         src_track = secondClip->getSubPlaylistIndex();
1719         first_src_track = firstClip->getSubPlaylistIndex();
1720     } else {
1721         return false;
1722     }
1723     bool result = false;
1724     bool closing = false;
1725     Fun local_undo = []() { return true; };
1726     Fun local_redo = []() { return true; };
1727     if (auto ptr = m_parent.lock()) {
1728         // Resize first part clip
1729         closing = ptr->m_closing;
1730         if (closing) {
1731             result = true;
1732         } else {
1733             result = ptr->getClipPtr(clipIds.first)->requestResize(secondInPos - firstInPos, true, local_undo, local_redo, true, true);
1734             // Resize main clip
1735             result = result && ptr->getClipPtr(clipIds.second)->requestResize(endPos - secondInPos, false, local_undo, local_redo, true, true);
1736         }
1737     }
1738     if (result) {
1739         QString assetId = m_sameCompositions[clipIds.second]->getAssetId();
1740         QVector<QPair<QString, QVariant>> params = m_sameCompositions[clipIds.second]->getAllParameters();
1741         std::pair<int, int> tracks = getMixTracks(clipIds.second);
1742         bool switchSecondTrack = false;
1743         bool switchFirstTrack = false;
1744         if (src_track == 1 && !secondClipHasEndMix && !closing) {
1745             switchSecondTrack = true;
1746         }
1747         if (first_src_track == 1 && !firstClipHasStartMix && !closing) {
1748             switchFirstTrack = true;
1749         }
1750         Fun replay = [this, clipIds, firstInPos, secondInPos, switchFirstTrack, switchSecondTrack]() {
1751             if (switchFirstTrack) {
1752                 // Revert clip to playlist 0 since it has no mix
1753                 switchPlaylist(clipIds.first, firstInPos, 1, 0);
1754             }
1755             if (switchSecondTrack) {
1756                 // Revert clip to playlist 0 since it has no mix
1757                 switchPlaylist(clipIds.second, secondInPos, 1, 0);
1758             }
1759             // Delete transition
1760             Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[clipIds.second]->getAsset());
1761             QScopedPointer<Mlt::Field> field(m_track->field());
1762             field->lock();
1763             field->disconnect_service(transition);
1764             field->unlock();
1765             m_sameCompositions.erase(clipIds.second);
1766             m_mixList.remove(clipIds.first);
1767             if (auto ptr = m_parent.lock()) {
1768                 std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(clipIds.second));
1769                 movedClip->setMixDuration(0);
1770                 /*QModelIndex ix = ptr->makeClipIndexFromID(clipIds.first);
1771                 Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::DurationRole});*/
1772                 QModelIndex ix2 = ptr->makeClipIndexFromID(clipIds.second);
1773                 Q_EMIT ptr->dataChanged(ix2, ix2, {TimelineModel::MixRole, TimelineModel::MixCutRole});
1774             }
1775             return true;
1776         };
1777         replay();
1778         Fun reverse = [this, clipIds, assetId, params, tracks, mixDuration, mixPosition, mixCutPos, firstInPos, secondInPos, switchFirstTrack,
1779                        switchSecondTrack]() {
1780             // First restore correct playlist
1781             if (switchFirstTrack) {
1782                 // Revert clip to playlist 1
1783                 switchPlaylist(clipIds.first, firstInPos, 0, 1);
1784             }
1785             if (switchSecondTrack) {
1786                 // Revert clip to playlist 1
1787                 switchPlaylist(clipIds.second, secondInPos, 0, 1);
1788             }
1789             // Build mix
1790             if (auto ptr = m_parent.lock()) {
1791                 std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(clipIds.second));
1792                 movedClip->setMixDuration(mixDuration, mixCutPos);
1793                 // Insert mix transition
1794                 std::unique_ptr<Mlt::Transition> t = TransitionsRepository::get()->getTransition(assetId);
1795                 t->set_in_and_out(mixPosition, mixPosition + mixDuration);
1796                 t->set("kdenlive:mixcut", mixCutPos);
1797                 t->set("kdenlive_id", assetId.toUtf8().constData());
1798                 t->set_tracks(tracks.first, tracks.second);
1799                 m_track->plant_transition(*t.get(), tracks.first, tracks.second);
1800                 QDomElement xml = TransitionsRepository::get()->getXml(assetId);
1801                 QDomNodeList xmlParams = xml.elementsByTagName(QStringLiteral("parameter"));
1802                 for (int i = 0; i < xmlParams.count(); ++i) {
1803                     QDomElement currentParameter = xmlParams.item(i).toElement();
1804                     QString paramName = currentParameter.attribute(QStringLiteral("name"));
1805                     for (const auto &p : qAsConst(params)) {
1806                         if (p.first == paramName) {
1807                             currentParameter.setAttribute(QStringLiteral("value"), p.second.toString());
1808                             break;
1809                         }
1810                     }
1811                 }
1812                 std::shared_ptr<AssetParameterModel> asset(
1813                     new AssetParameterModel(std::move(t), xml, assetId, ObjectId(KdenliveObjectType::TimelineMix, clipIds.second, ptr->uuid()), QString()));
1814                 m_sameCompositions[clipIds.second] = asset;
1815                 m_mixList.insert(clipIds.first, clipIds.second);
1816                 QModelIndex ix2 = ptr->makeClipIndexFromID(clipIds.second);
1817                 Q_EMIT ptr->dataChanged(ix2, ix2, {TimelineModel::MixRole, TimelineModel::MixCutRole});
1818             }
1819             return true;
1820         };
1821         PUSH_LAMBDA(replay, local_redo);
1822         PUSH_FRONT_LAMBDA(reverse, local_undo);
1823         UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
1824         return true;
1825     }
1826     return false;
1827 }
1828 
1829 bool TrackModel::requestClipMix(const QString &mixId, std::pair<int, int> clipIds, std::pair<int, int> mixDurations, bool updateView, bool finalMove, Fun &undo,
1830                                 Fun &redo, bool groupMove)
1831 {
1832     QWriteLocker locker(&m_lock);
1833     // By default, insertion occurs in topmost track
1834     // Find out the clip id at position
1835     int firstClipPos;
1836     int secondClipPos;
1837     int secondClipDuration;
1838     int firstClipDuration;
1839     int source_track;
1840     int mixPosition;
1841     int secondClipCut = 0;
1842     int dest_track = 1;
1843     bool remixPlaylists = false;
1844     bool clipHasEndMix = false;
1845     if (auto ptr = m_parent.lock()) {
1846         // The clip that will be moved to playlist 1
1847         std::shared_ptr<ClipModel> secondClip(ptr->getClipPtr(clipIds.second));
1848         secondClipDuration = secondClip->getPlaytime();
1849         secondClipPos = secondClip->getPosition();
1850         source_track = secondClip->getSubPlaylistIndex();
1851         std::shared_ptr<ClipModel> firstClip(ptr->getClipPtr(clipIds.first));
1852         firstClipDuration = firstClip->getPlaytime();
1853         // Ensure mix is not longer than clip and doesn't overlap other mixes
1854         firstClipPos = firstClip->getPosition();
1855         if (firstClip->getSubPlaylistIndex() == 1) {
1856             dest_track = 0;
1857         }
1858         mixPosition = qMax(firstClipPos, secondClipPos - mixDurations.second);
1859         int maxPos = qMin(secondClipPos + secondClipDuration, secondClipPos + mixDurations.first);
1860         if (hasStartMix(clipIds.first)) {
1861             std::pair<MixInfo, MixInfo> mixData = getMixInfo(clipIds.first);
1862             mixPosition = qMax(mixData.first.firstClipInOut.second, mixPosition);
1863         }
1864         if (hasEndMix(clipIds.second)) {
1865             std::pair<MixInfo, MixInfo> mixData = getMixInfo(clipIds.second);
1866             clipHasEndMix = true;
1867             maxPos = qMin(mixData.second.secondClipInOut.first, maxPos);
1868             if (ptr->m_allClips[mixData.second.secondClipId]->getSubPlaylistIndex() == dest_track) {
1869                 remixPlaylists = true;
1870             }
1871         }
1872         mixDurations.first = qMin(mixDurations.first, maxPos - mixPosition);
1873         secondClipCut = maxPos - secondClipPos;
1874     } else {
1875         // Error, timeline unavailable
1876         qDebug() << "=== ERROR NO TIMELINE!!!!";
1877         return false;
1878     }
1879 
1880     // Rearrange subsequent mixes
1881     Fun rearrange_playlists = []() { return true; };
1882     Fun rearrange_playlists_undo = []() { return true; };
1883     if (remixPlaylists && source_track != dest_track) {
1884         // A list of clip ids x playlists
1885         QMap<int, int> rearrangedPlaylists;
1886         QMap<int, QVector<QPair<QString, QVariant>>> mixParameters;
1887         int moveId = m_mixList.value(clipIds.second, -1);
1888         while (moveId > -1) {
1889             int current = m_allClips[moveId]->getSubPlaylistIndex();
1890             rearrangedPlaylists.insert(moveId, current);
1891             QVector<QPair<QString, QVariant>> params = m_sameCompositions.at(moveId)->getAllParameters();
1892             mixParameters.insert(moveId, params);
1893             if (hasEndMix(moveId)) {
1894                 moveId = m_mixList.value(moveId, -1);
1895             } else {
1896                 break;
1897             }
1898         }
1899         rearrange_playlists = [this, map = rearrangedPlaylists]() {
1900             // First, remove all clips on playlist 0
1901             QMapIterator<int, int> i(map);
1902             while (i.hasNext()) {
1903                 i.next();
1904                 if (i.value() == 0) {
1905                     int target_clip = m_playlists[0].get_clip_index_at(m_allClips[i.key()]->getPosition());
1906                     std::unique_ptr<Mlt::Producer> prod(m_playlists[0].replace_with_blank(target_clip));
1907                 }
1908             }
1909             m_playlists[0].consolidate_blanks();
1910             // Then move all clips from playlist 1 to playlist 0
1911             i.toFront();
1912             if (auto ptr = m_parent.lock()) {
1913                 while (i.hasNext()) {
1914                     i.next();
1915                     if (i.value() == 1) {
1916                         // Remove
1917                         int pos = m_allClips[i.key()]->getPosition();
1918                         int target_clip = m_playlists[1].get_clip_index_at(pos);
1919                         std::unique_ptr<Mlt::Producer> prod(m_playlists[1].replace_with_blank(target_clip));
1920                         prod.reset();
1921                         // Replug
1922                         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(i.key());
1923                         clip->setSubPlaylistIndex(0, m_id);
1924                         int index = m_playlists[0].insert_at(pos, *clip, 1);
1925                         m_playlists[0].consolidate_blanks();
1926                         if (index == -1) {
1927                             // Something went wrong, abort
1928                             m_playlists[1].insert_at(pos, *clip, 1);
1929                             m_playlists[1].consolidate_blanks();
1930                             return false;
1931                         }
1932                     }
1933                 }
1934                 m_playlists[1].consolidate_blanks();
1935                 // Finally replug playlist 0 clips in playlist 1 and fix transition direction
1936                 i.toFront();
1937                 while (i.hasNext()) {
1938                     i.next();
1939                     if (i.value() == 0) {
1940                         int pos = m_allClips[i.key()]->getPosition();
1941                         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(i.key());
1942                         clip->setSubPlaylistIndex(1, m_id);
1943                         int index = m_playlists[1].insert_at(pos, *clip, 1);
1944                         m_playlists[1].consolidate_blanks();
1945                         if (index == -1) {
1946                             // Something went wrong, abort
1947                             m_playlists[0].insert_at(pos, *clip, 1);
1948                             m_playlists[0].consolidate_blanks();
1949                             return false;
1950                         }
1951                     }
1952                     std::unique_ptr<Mlt::Field> field(m_track->field());
1953                     field->block();
1954                     if (m_sameCompositions.count(i.key()) > 0) {
1955                         // There is a mix at clip start, adjust direction
1956                         Mlt::Properties *props = m_sameCompositions[i.key()]->getAsset();
1957                         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[i.key()]->getAsset());
1958                         bool reverse = i.value() == 1;
1959                         const QString assetName = m_sameCompositions[i.key()]->getAssetId();
1960                         const ObjectId ownerId = m_sameCompositions[i.key()]->getOwnerId();
1961                         field->disconnect_service(transition);
1962                         std::unique_ptr<Mlt::Transition> newTrans = TransitionsRepository::get()->getTransition(m_sameCompositions[i.key()]->getAssetId());
1963                         newTrans->inherit(*props);
1964                         updateCompositionDirection(*newTrans.get(), reverse);
1965                         m_sameCompositions.erase(i.key());
1966                         QDomElement xml = TransitionsRepository::get()->getXml(assetName);
1967                         std::shared_ptr<AssetParameterModel> asset(new AssetParameterModel(std::move(newTrans), xml, assetName, ownerId, QString()));
1968                         m_sameCompositions[i.key()] = asset;
1969                     }
1970                     field->unblock();
1971                 }
1972                 return true;
1973             } else {
1974                 return false;
1975             }
1976         };
1977         rearrange_playlists_undo = [this, map = rearrangedPlaylists, mixParameters]() {
1978             // First, remove all clips on playlist 1
1979             QMapIterator<int, int> i(map);
1980             while (i.hasNext()) {
1981                 i.next();
1982                 if (i.value() == 0) {
1983                     int target_clip = m_playlists[1].get_clip_index_at(m_allClips[i.key()]->getPosition());
1984                     std::unique_ptr<Mlt::Producer> prod(m_playlists[1].replace_with_blank(target_clip));
1985                 }
1986                 m_playlists[1].consolidate_blanks();
1987             }
1988             // Then move all clips from playlist 0 to playlist 1
1989             i.toFront();
1990             if (auto ptr = m_parent.lock()) {
1991                 while (i.hasNext()) {
1992                     i.next();
1993                     if (i.value() == 1) {
1994                         // Remove
1995                         int pos = m_allClips[i.key()]->getPosition();
1996                         int target_clip = m_playlists[0].get_clip_index_at(pos);
1997                         std::unique_ptr<Mlt::Producer> prod(m_playlists[0].replace_with_blank(target_clip));
1998                         // Replug
1999                         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(i.key());
2000                         clip->setSubPlaylistIndex(1, m_id);
2001                         int index = m_playlists[1].insert_at(pos, *clip, 1);
2002                         m_playlists[1].consolidate_blanks();
2003                         if (index == -1) {
2004                             // Something went wrong, abort
2005                             m_playlists[0].insert_at(pos, *clip, 1);
2006                             m_playlists[0].consolidate_blanks();
2007                             return false;
2008                         }
2009                     }
2010                     m_playlists[0].consolidate_blanks();
2011                 }
2012                 // Finally replug playlist 1 clips in playlist 0 and fix transition direction
2013                 i.toFront();
2014                 while (i.hasNext()) {
2015                     i.next();
2016                     if (i.value() == 0) {
2017                         int pos = m_allClips[i.key()]->getPosition();
2018                         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(i.key());
2019                         clip->setSubPlaylistIndex(0, m_id);
2020                         int index = m_playlists[0].insert_at(pos, *clip, 1);
2021                         m_playlists[0].consolidate_blanks();
2022                         if (index == -1) {
2023                             // Something went wrong, abort
2024                             m_playlists[1].insert_at(pos, *clip, 1);
2025                             m_playlists[1].consolidate_blanks();
2026                             return false;
2027                         }
2028                     }
2029                     std::unique_ptr<Mlt::Field> field(m_track->field());
2030                     field->block();
2031                     if (m_sameCompositions.count(i.key()) > 0) {
2032                         // There is a mix at clip start, adjust direction
2033                         Mlt::Properties *props = m_sameCompositions[i.key()]->getAsset();
2034                         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[i.key()]->getAsset());
2035                         bool reverse = i.value() == 0;
2036                         const QString assetName = m_sameCompositions[i.key()]->getAssetId();
2037                         const ObjectId ownerId = m_sameCompositions[i.key()]->getOwnerId();
2038                         field->disconnect_service(transition);
2039                         std::unique_ptr<Mlt::Transition> newTrans = TransitionsRepository::get()->getTransition(m_sameCompositions[i.key()]->getAssetId());
2040                         newTrans->inherit(*props);
2041                         updateCompositionDirection(*newTrans.get(), reverse);
2042                         m_sameCompositions.erase(i.key());
2043                         QDomElement xml = TransitionsRepository::get()->getXml(assetName);
2044                         std::shared_ptr<AssetParameterModel> asset(new AssetParameterModel(std::move(newTrans), xml, assetName, ownerId, QString()));
2045                         m_sameCompositions[i.key()] = asset;
2046                     }
2047                     field->unblock();
2048                 }
2049                 return true;
2050             } else {
2051                 return false;
2052             }
2053         };
2054     }
2055     // Create mix compositing
2056     Fun build_mix = [clipIds, mixPosition, mixDurations, dest_track, secondClipCut, mixId, this]() {
2057         if (auto ptr = m_parent.lock()) {
2058             std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(clipIds.second));
2059             movedClip->setMixDuration(mixDurations.first + mixDurations.second, secondClipCut);
2060             // Insert mix transition
2061             QString assetName;
2062             std::unique_ptr<Mlt::Transition> t;
2063             QDomElement xml;
2064             if (isAudioTrack()) {
2065                 // Mix is the only audio transition
2066                 t = TransitionsRepository::get()->getTransition(QStringLiteral("mix"));
2067                 t->set_in_and_out(mixPosition, mixPosition + mixDurations.first + mixDurations.second);
2068                 t->set("kdenlive:mixcut", secondClipCut);
2069                 t->set("start", -1);
2070                 t->set("accepts_blanks", 1);
2071                 assetName = QStringLiteral("mix");
2072                 xml = TransitionsRepository::get()->getXml(assetName);
2073                 if (dest_track == 0) {
2074                     t->set("reverse", 1);
2075                     Xml::setXmlParameter(xml, QStringLiteral("reverse"), QStringLiteral("1"));
2076                 }
2077                 m_track->plant_transition(*t.get(), 0, 1);
2078             } else {
2079                 assetName = mixId.isEmpty() || mixId == QLatin1String("mix") ? QStringLiteral("luma") : mixId;
2080                 t = TransitionsRepository::get()->getTransition(assetName);
2081                 t->set_in_and_out(mixPosition, mixPosition + mixDurations.first + mixDurations.second);
2082                 xml = TransitionsRepository::get()->getXml(assetName);
2083                 t->set("kdenlive:mixcut", secondClipCut);
2084                 t->set("kdenlive_id", assetName.toUtf8().constData());
2085                 if (dest_track == 0) {
2086                     t->set_tracks(1, 0);
2087                     m_track->plant_transition(*t.get(), 1, 0);
2088                 } else {
2089                     t->set_tracks(0, 1);
2090                     m_track->plant_transition(*t.get(), 0, 1);
2091                 }
2092             }
2093             std::shared_ptr<AssetParameterModel> asset(
2094                 new AssetParameterModel(std::move(t), xml, assetName, ObjectId(KdenliveObjectType::TimelineMix, clipIds.second, ptr->uuid()), QString()));
2095             m_sameCompositions[clipIds.second] = asset;
2096             m_mixList.insert(clipIds.first, clipIds.second);
2097         }
2098         return true;
2099     };
2100 
2101     Fun destroy_mix = [clipIds, this]() {
2102         if (auto ptr = m_parent.lock()) {
2103             if (m_sameCompositions.count(clipIds.second) == 0) {
2104                 // Mix was already deleted
2105                 if (m_mixList.contains(clipIds.first)) {
2106                     m_mixList.remove(clipIds.first);
2107                 }
2108                 return true;
2109             }
2110             // Clear asset panel if mix was selected
2111             if (ptr->m_selectedMix == clipIds.second) {
2112                 ptr->requestClearAssetView(clipIds.second);
2113             }
2114             Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[clipIds.second]->getAsset());
2115             std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(clipIds.second));
2116             movedClip->setMixDuration(0);
2117             QModelIndex ix = ptr->makeClipIndexFromID(clipIds.second);
2118             Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::StartRole, TimelineModel::MixRole, TimelineModel::MixCutRole});
2119             QScopedPointer<Mlt::Field> field(m_track->field());
2120             field->lock();
2121             field->disconnect_service(transition);
2122             field->unlock();
2123             m_sameCompositions.erase(clipIds.second);
2124             m_mixList.remove(clipIds.first);
2125         }
2126         return true;
2127     };
2128     // lock MLT playlist so that we don't end up with invalid frames in monitor
2129     auto operation = requestClipDeletion_lambda(clipIds.second, updateView, finalMove, groupMove, false);
2130     bool res = operation();
2131     if (res) {
2132         Fun replay = [this, clipIds, dest_track, firstClipPos, secondClipDuration, mixPosition, mixDurations, build_mix, secondClipPos, clipHasEndMix,
2133                       updateView, finalMove, groupMove, rearrange_playlists]() {
2134             if (auto ptr = m_parent.lock()) {
2135                 ptr->getClipPtr(clipIds.second)->setSubPlaylistIndex(dest_track, m_id);
2136             }
2137             bool result = rearrange_playlists();
2138             auto op = requestClipInsertion_lambda(clipIds.second, secondClipPos, updateView, finalMove, groupMove);
2139             result = result && op();
2140             if (result) {
2141                 build_mix();
2142                 std::function<bool(void)> local_undo = []() { return true; };
2143                 std::function<bool(void)> local_redo = []() { return true; };
2144                 if (auto ptr = m_parent.lock()) {
2145                     result = ptr->getClipPtr(clipIds.second)
2146                                  ->requestResize(secondClipPos + secondClipDuration - mixPosition, false, local_undo, local_redo, true, clipHasEndMix);
2147                     result = result && ptr->getClipPtr(clipIds.first)
2148                                            ->requestResize(mixPosition + mixDurations.first + mixDurations.second - firstClipPos, true, local_undo, local_redo,
2149                                                            true, true);
2150                     QModelIndex ix = ptr->makeClipIndexFromID(clipIds.second);
2151                     Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::StartRole, TimelineModel::MixRole, TimelineModel::MixCutRole});
2152                 }
2153             }
2154             return result;
2155         };
2156 
2157         Fun reverse = [this, clipIds, source_track, secondClipDuration, firstClipDuration, destroy_mix, secondClipPos, updateView, finalMove, groupMove,
2158                        operation, rearrange_playlists_undo]() {
2159             destroy_mix();
2160             std::function<bool(void)> local_undo = []() { return true; };
2161             std::function<bool(void)> local_redo = []() { return true; };
2162             if (auto ptr = m_parent.lock()) {
2163                 ptr->getClipPtr(clipIds.second)->requestResize(secondClipDuration, false, local_undo, local_redo, false);
2164                 ptr->getClipPtr(clipIds.first)->requestResize(firstClipDuration, true, local_undo, local_redo, false);
2165             }
2166             bool result = operation();
2167             if (auto ptr = m_parent.lock()) {
2168                 ptr->getClipPtr(clipIds.second)->setSubPlaylistIndex(source_track, m_id);
2169             }
2170             result = result && rearrange_playlists_undo();
2171             auto op = requestClipInsertion_lambda(clipIds.second, secondClipPos, updateView, finalMove, groupMove);
2172             result = result && op();
2173             return result;
2174         };
2175         res = replay();
2176         if (res) {
2177             PUSH_LAMBDA(replay, operation);
2178             UPDATE_UNDO_REDO(operation, reverse, undo, redo);
2179         } else {
2180             qDebug() << "=============\nSECOND INSERT FAILED\n\n=================";
2181             reverse();
2182         }
2183     } else {
2184         qDebug() << "=== CLIP DELETION FAILED";
2185     }
2186     return res;
2187 }
2188 
2189 std::pair<MixInfo, MixInfo> TrackModel::getMixInfo(int clipId) const
2190 {
2191     std::pair<MixInfo, MixInfo> result;
2192     MixInfo startMix;
2193     MixInfo endMix;
2194     if (m_sameCompositions.count(clipId) > 0) {
2195         // There is a mix at clip start
2196         startMix.firstClipId = m_mixList.key(clipId, -1);
2197         startMix.secondClipId = clipId;
2198         if (auto ptr = m_parent.lock()) {
2199             if (ptr->isClip(startMix.firstClipId)) {
2200                 std::shared_ptr<ClipModel> clip1 = ptr->getClipPtr(startMix.firstClipId);
2201                 std::shared_ptr<ClipModel> clip2 = ptr->getClipPtr(startMix.secondClipId);
2202                 startMix.firstClipInOut.first = clip1->getPosition();
2203                 startMix.firstClipInOut.second = startMix.firstClipInOut.first + clip1->getPlaytime();
2204                 startMix.secondClipInOut.first = clip2->getPosition();
2205                 startMix.secondClipInOut.second = startMix.secondClipInOut.first + clip2->getPlaytime();
2206                 startMix.mixOffset = clip2->getMixCutPosition();
2207             } else {
2208                 // Clip was deleted
2209                 startMix.firstClipId = -1;
2210             }
2211         }
2212     } else {
2213         startMix.firstClipId = -1;
2214         startMix.secondClipId = -1;
2215     }
2216     int secondClip = m_mixList.value(clipId, -1);
2217     if (secondClip > -1) {
2218         // There is a mix at clip end
2219         endMix.firstClipId = clipId;
2220         endMix.secondClipId = secondClip;
2221         if (auto ptr = m_parent.lock()) {
2222             if (ptr->isClip(secondClip)) {
2223                 std::shared_ptr<ClipModel> clip1 = ptr->getClipPtr(endMix.firstClipId);
2224                 std::shared_ptr<ClipModel> clip2 = ptr->getClipPtr(endMix.secondClipId);
2225                 endMix.firstClipInOut.first = clip1->getPosition();
2226                 endMix.firstClipInOut.second = endMix.firstClipInOut.first + clip1->getPlaytime();
2227                 endMix.secondClipInOut.first = clip2->getPosition();
2228                 endMix.secondClipInOut.second = endMix.secondClipInOut.first + clip2->getPlaytime();
2229             } else {
2230                 // Clip was deleted
2231                 endMix.firstClipId = -1;
2232             }
2233         }
2234     } else {
2235         endMix.firstClipId = -1;
2236         endMix.secondClipId = -1;
2237     }
2238     result = {startMix, endMix};
2239     return result;
2240 }
2241 
2242 bool TrackModel::deleteMix(int clipId, bool final, bool notify)
2243 {
2244     Q_ASSERT(m_sameCompositions.count(clipId) > 0);
2245     if (auto ptr = m_parent.lock()) {
2246         if (notify) {
2247             std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(clipId));
2248             movedClip->setMixDuration(final ? 0 : 1);
2249             QModelIndex ix = ptr->makeClipIndexFromID(clipId);
2250             Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::StartRole, TimelineModel::MixRole, TimelineModel::MixCutRole});
2251         }
2252         if (final) {
2253             Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[clipId]->getAsset());
2254             QScopedPointer<Mlt::Field> field(m_track->field());
2255             field->lock();
2256             field->disconnect_service(transition);
2257             field->unlock();
2258             m_sameCompositions.erase(clipId);
2259             int firstClip = m_mixList.key(clipId, -1);
2260             if (firstClip > -1) {
2261                 m_mixList.remove(firstClip);
2262             }
2263         }
2264         return true;
2265     }
2266     return false;
2267 }
2268 
2269 bool TrackModel::createMix(MixInfo info, std::pair<QString, QVector<QPair<QString, QVariant>>> params, std::pair<int, int> tracks, bool finalMove)
2270 {
2271     if (m_sameCompositions.count(info.secondClipId) > 0) {
2272         // Clip already has a mix
2273         Q_ASSERT(false);
2274         return false;
2275     }
2276     if (auto ptr = m_parent.lock()) {
2277         // Insert mix transition
2278         std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(info.secondClipId));
2279         int in = movedClip->getPosition();
2280         // int out = in + info.firstClipInOut.second - info.secondClipInOut.first;
2281         int duration = info.firstClipInOut.second - info.secondClipInOut.first;
2282         int out = in + duration;
2283         movedClip->setMixDuration(duration, info.mixOffset);
2284         std::unique_ptr<Mlt::Transition> t;
2285         const QString assetId = params.first;
2286         t = std::make_unique<Mlt::Transition>(pCore->getProjectProfile().get_profile(), assetId.toUtf8().constData());
2287         t->set_in_and_out(in, out);
2288         t->set("kdenlive:mixcut", info.mixOffset);
2289         t->set("kdenlive_id", assetId.toUtf8().constData());
2290         t->set_tracks(tracks.first, tracks.second);
2291         m_track->plant_transition(*t.get(), tracks.first, tracks.second);
2292         QDomElement xml = TransitionsRepository::get()->getXml(assetId);
2293         QDomNodeList xmlParams = xml.elementsByTagName(QStringLiteral("parameter"));
2294         for (int i = 0; i < xmlParams.count(); ++i) {
2295             QDomElement currentParameter = xmlParams.item(i).toElement();
2296             QString paramName = currentParameter.attribute(QStringLiteral("name"));
2297             for (const auto &p : qAsConst(params.second)) {
2298                 if (p.first == paramName) {
2299                     currentParameter.setAttribute(QStringLiteral("value"), p.second.toString());
2300                     break;
2301                 }
2302             }
2303         }
2304         std::shared_ptr<AssetParameterModel> asset(
2305             new AssetParameterModel(std::move(t), xml, assetId, ObjectId(KdenliveObjectType::TimelineMix, info.secondClipId, ptr->uuid()), QString()));
2306         m_sameCompositions[info.secondClipId] = asset;
2307         m_mixList.insert(info.firstClipId, info.secondClipId);
2308         if (finalMove) {
2309             QModelIndex ix2 = ptr->makeClipIndexFromID(info.secondClipId);
2310             Q_EMIT ptr->dataChanged(ix2, ix2, {TimelineModel::MixRole, TimelineModel::MixCutRole});
2311         }
2312         return true;
2313     }
2314     qDebug() << "== COULD NOT PLANT MIX; TRACK UNAVAILABLE";
2315     return false;
2316 }
2317 
2318 bool TrackModel::createMix(MixInfo info, bool isAudio)
2319 {
2320     if (m_sameCompositions.count(info.secondClipId) > 0) {
2321         return false;
2322     }
2323     if (auto ptr = m_parent.lock()) {
2324         // Insert mix transition
2325         std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(info.secondClipId));
2326         int in = movedClip->getPosition();
2327         // int out = in + info.firstClipInOut.second - info.secondClipInOut.first;
2328         int out = in + movedClip->getMixDuration();
2329         movedClip->setMixDuration(out - in);
2330         bool reverse = movedClip->getSubPlaylistIndex() == 0;
2331         QString assetName;
2332         std::unique_ptr<Mlt::Transition> t;
2333         if (isAudio) {
2334             t = std::make_unique<Mlt::Transition>(pCore->getProjectProfile().get_profile(), "mix");
2335             t->set_in_and_out(in, out);
2336             t->set("start", -1);
2337             t->set("accepts_blanks", 1);
2338             if (reverse) {
2339                 t->set("reverse", 1);
2340             }
2341             m_track->plant_transition(*t.get(), 0, 1);
2342             assetName = QStringLiteral("mix");
2343         } else {
2344             t = std::make_unique<Mlt::Transition>(pCore->getProjectProfile().get_profile(), "luma");
2345             t->set_in_and_out(in, out);
2346             t->set("kdenlive_id", "luma");
2347             if (reverse) {
2348                 t->set_tracks(1, 0);
2349                 m_track->plant_transition(*t.get(), 1, 0);
2350             } else {
2351                 t->set_tracks(0, 1);
2352                 m_track->plant_transition(*t.get(), 0, 1);
2353             }
2354             /*if (reverse) {
2355                 t->set("reverse", 1);
2356             }
2357             m_track->plant_transition(*t.get(), 0, 1);*/
2358             assetName = QStringLiteral("luma");
2359         }
2360         QDomElement xml = TransitionsRepository::get()->getXml(assetName);
2361         std::shared_ptr<AssetParameterModel> asset(
2362             new AssetParameterModel(std::move(t), xml, assetName, ObjectId(KdenliveObjectType::TimelineMix, info.secondClipId, ptr->uuid()), QString()));
2363         m_sameCompositions[info.secondClipId] = asset;
2364         m_mixList.insert(info.firstClipId, info.secondClipId);
2365         return true;
2366     }
2367     return false;
2368 }
2369 
2370 bool TrackModel::createMix(std::pair<int, int> clipIds, std::pair<int, int> mixData)
2371 {
2372     if (m_sameCompositions.count(clipIds.second) > 0) {
2373         return false;
2374     }
2375     if (auto ptr = m_parent.lock()) {
2376         std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(clipIds.second));
2377         movedClip->setMixDuration(mixData.second);
2378         QModelIndex ix = ptr->makeClipIndexFromID(clipIds.second);
2379         Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::MixRole, TimelineModel::MixCutRole});
2380         bool reverse = movedClip->getSubPlaylistIndex() == 0;
2381         // Insert mix transition
2382         QString assetName;
2383         std::unique_ptr<Mlt::Transition> t;
2384         if (isAudioTrack()) {
2385             t = std::make_unique<Mlt::Transition>(pCore->getProjectProfile().get_profile(), "mix");
2386             t->set_in_and_out(mixData.first, mixData.first + mixData.second);
2387             t->set("start", -1);
2388             t->set("accepts_blanks", 1);
2389             if (reverse) {
2390                 t->set("reverse", 1);
2391             }
2392             m_track->plant_transition(*t.get(), 0, 1);
2393             assetName = QStringLiteral("mix");
2394         } else {
2395             t = std::make_unique<Mlt::Transition>(pCore->getProjectProfile().get_profile(), "luma");
2396             t->set("kdenlive_id", "luma");
2397             t->set_in_and_out(mixData.first, mixData.first + mixData.second);
2398             if (reverse) {
2399                 t->set_tracks(1, 0);
2400                 m_track->plant_transition(*t.get(), 1, 0);
2401             } else {
2402                 t->set_tracks(0, 1);
2403                 m_track->plant_transition(*t.get(), 0, 1);
2404             }
2405             /*if (reverse) {
2406                 t->set("reverse", 1);
2407             }
2408             m_track->plant_transition(*t.get(), 0, 1);*/
2409             assetName = QStringLiteral("luma");
2410         }
2411         QDomElement xml = TransitionsRepository::get()->getXml(assetName);
2412         std::shared_ptr<AssetParameterModel> asset(
2413             new AssetParameterModel(std::move(t), xml, assetName, ObjectId(KdenliveObjectType::TimelineMix, clipIds.second, ptr->uuid()), QString()));
2414         m_sameCompositions[clipIds.second] = asset;
2415         m_mixList.insert(clipIds.first, clipIds.second);
2416         return true;
2417     }
2418     return false;
2419 }
2420 
2421 void TrackModel::setMixDuration(int cid, int mixDuration, int mixCut)
2422 {
2423     m_allClips[cid]->setMixDuration(mixDuration, mixCut);
2424     m_sameCompositions[cid]->getAsset()->set("kdenlive:mixcut", mixCut);
2425     int in = m_allClips[cid]->getPosition();
2426     int out = in + mixDuration;
2427     Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[cid]->getAsset());
2428     transition.set_in_and_out(in, out);
2429     Q_EMIT m_sameCompositions[cid]->dataChanged(QModelIndex(), QModelIndex(), {AssetParameterModel::ParentDurationRole});
2430 }
2431 
2432 int TrackModel::getMixDuration(int cid) const
2433 {
2434     Q_ASSERT(m_sameCompositions.count(cid) > 0);
2435     Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions.at(cid)->getAsset());
2436     return transition.get_length() - 1;
2437 }
2438 
2439 void TrackModel::removeMix(const MixInfo &info)
2440 {
2441     Q_ASSERT(m_sameCompositions.count(info.secondClipId) > 0);
2442     Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[info.secondClipId]->getAsset());
2443     QScopedPointer<Mlt::Field> field(m_track->field());
2444     field->lock();
2445     field->disconnect_service(transition);
2446     field->unlock();
2447     m_sameCompositions.erase(info.secondClipId);
2448     m_mixList.remove(info.firstClipId);
2449 }
2450 
2451 void TrackModel::syncronizeMixes(bool finalMove)
2452 {
2453     QList<int> toDelete;
2454     for (const auto &n : m_sameCompositions) {
2455         // qDebug() << "Key:[" << n.first << "] Value:[" << n.second << "]\n";
2456         int secondClipId = n.first;
2457         int firstClip = m_mixList.key(secondClipId, -1);
2458         Q_ASSERT(firstClip > -1);
2459         if (m_allClips.find(firstClip) == m_allClips.end() || m_allClips.find(secondClipId) == m_allClips.end()) {
2460             // One of the clip was removed, delete the mix
2461             qDebug() << "=== CLIPS: " << firstClip << " / " << secondClipId << " ARE MISSING!!!!";
2462             Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[secondClipId]->getAsset());
2463             QScopedPointer<Mlt::Field> field(m_track->field());
2464             field->lock();
2465             field->disconnect_service(transition);
2466             field->unlock();
2467             toDelete << secondClipId;
2468             m_mixList.remove(firstClip);
2469             continue;
2470         }
2471         // Asjust mix in/out
2472         int mixIn = m_allClips[secondClipId]->getPosition();
2473         int mixOut = m_allClips[firstClip]->getPosition() + m_allClips[firstClip]->getPlaytime();
2474         if (mixOut <= mixIn) {
2475             if (finalMove) {
2476                 // Delete mix
2477                 mixOut = mixIn;
2478             } else {
2479                 mixOut = mixIn + 1;
2480             }
2481         }
2482         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[secondClipId]->getAsset());
2483         if (mixIn == mixOut) {
2484             QScopedPointer<Mlt::Field> field(m_track->field());
2485             field->lock();
2486             field->disconnect_service(transition);
2487             field->unlock();
2488             toDelete << secondClipId;
2489             m_mixList.remove(firstClip);
2490         } else {
2491             transition.set_in_and_out(mixIn, mixOut);
2492         }
2493         if (auto ptr = m_parent.lock()) {
2494             ptr->getClipPtr(secondClipId)->setMixDuration(mixOut - mixIn);
2495             QModelIndex ix = ptr->makeClipIndexFromID(secondClipId);
2496             Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::MixRole, TimelineModel::MixCutRole});
2497         }
2498     }
2499     for (int i : qAsConst(toDelete)) {
2500         m_sameCompositions.erase(i);
2501     }
2502 }
2503 
2504 int TrackModel::mixCount() const
2505 {
2506     Q_ASSERT(m_mixList.size() == int(m_sameCompositions.size()));
2507     return m_mixList.size();
2508 }
2509 
2510 bool TrackModel::hasMix(int cid) const
2511 {
2512     if (m_mixList.contains(cid)) {
2513         return true;
2514     }
2515     if (m_sameCompositions.count(cid) > 0) {
2516         return true;
2517     }
2518     return false;
2519 }
2520 
2521 bool TrackModel::hasStartMix(int cid) const
2522 {
2523     return m_sameCompositions.count(cid) > 0;
2524 }
2525 
2526 int TrackModel::isOnCut(int cid)
2527 {
2528     if (auto ptr = m_parent.lock()) {
2529         std::shared_ptr<CompositionModel> composition = ptr->getCompositionPtr(cid);
2530         // Start and end pos are incremented by 1 to account snapping
2531         int startPos = composition->getPosition() - 1;
2532         int endPos = startPos + composition->getPlaytime() + 1;
2533         int cid1 = getClipByPosition(startPos);
2534         int cid2 = getClipByPosition(endPos);
2535         if (cid1 == -1 || cid2 == -1 || cid1 == cid2) {
2536             return -1;
2537         }
2538         if (m_mixList.contains(cid1) || m_sameCompositions.count(cid2) > 0) {
2539             // Clips are already mixed, abort
2540             return -1;
2541         }
2542         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(cid2);
2543         int mixRight = clip->getPosition();
2544         std::shared_ptr<ClipModel> clip2 = ptr->getClipPtr(cid1);
2545         int mixLeft = clip2->getPosition() + clip2->getPlaytime();
2546         if (mixLeft == mixRight) {
2547             return mixLeft;
2548         }
2549     }
2550     return -1;
2551 }
2552 
2553 bool TrackModel::hasEndMix(int cid) const
2554 {
2555     return m_mixList.contains(cid);
2556 }
2557 
2558 int TrackModel::getSecondMixPartner(int cid) const
2559 {
2560     return hasEndMix(cid) ? m_mixList.find(cid).value() : -1;
2561 }
2562 
2563 QDomElement TrackModel::mixXml(QDomDocument &document, int cid) const
2564 {
2565     QDomElement container = document.createElement(QStringLiteral("mix"));
2566     int firstClipId = m_mixList.key(cid);
2567     container.setAttribute(QStringLiteral("firstClip"), firstClipId);
2568     container.setAttribute(QStringLiteral("secondClip"), cid);
2569     if (auto ptr = m_parent.lock()) {
2570         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(firstClipId);
2571         container.setAttribute(QStringLiteral("firstClipPosition"), clip->getPosition());
2572     }
2573     const QString assetId = m_sameCompositions.at(cid)->getAssetId();
2574     QVector<QPair<QString, QVariant>> params = m_sameCompositions.at(cid)->getAllParameters();
2575     container.setAttribute(QStringLiteral("asset"), assetId);
2576     for (const auto &p : qAsConst(params)) {
2577         QDomElement para = document.createElement(QStringLiteral("param"));
2578         para.setAttribute(QStringLiteral("name"), p.first);
2579         QDomText val = document.createTextNode(p.second.toString());
2580         para.appendChild(val);
2581         container.appendChild(para);
2582     }
2583     std::pair<int, int> tracks = getMixTracks(cid);
2584     container.setAttribute(QStringLiteral("a_track"), tracks.first);
2585     container.setAttribute(QStringLiteral("b_track"), tracks.second);
2586 
2587     std::pair<MixInfo, MixInfo> mixData = getMixInfo(cid);
2588     container.setAttribute(QStringLiteral("mixStart"), mixData.first.secondClipInOut.first);
2589     container.setAttribute(QStringLiteral("mixEnd"), mixData.first.firstClipInOut.second);
2590     container.setAttribute(QStringLiteral("mixOffset"), mixData.first.mixOffset);
2591     return container;
2592 }
2593 
2594 bool TrackModel::loadMix(Mlt::Transition *t)
2595 {
2596     int in = t->get_in();
2597     int out = t->get_out() - 1;
2598     bool reverse = t->get_int("reverse") == 1;
2599     int cid1 = getClipByPosition(in, reverse ? 1 : 0);
2600     int cid2 = getClipByPosition(out, reverse ? 0 : 1);
2601     if (cid1 < 0 || cid2 < 0) {
2602         qDebug() << "INVALID CLIP MIX: " << cid1 << " - " << cid2;
2603         // Check if reverse setting was not correctly set
2604         cid1 = getClipByPosition(in, reverse ? 0 : 1);
2605         cid2 = getClipByPosition(out, reverse ? 1 : 0);
2606         if (cid1 < 0 || cid2 < 0) {
2607             QScopedPointer<Mlt::Field> field(m_track->field());
2608             field->lock();
2609             field->disconnect_service(*t);
2610             field->unlock();
2611             return false;
2612         }
2613     } else {
2614         int firstClipIn = m_allClips[cid1]->getPosition();
2615         if (in == firstClipIn && in != m_allClips[cid2]->getPosition()) {
2616             qDebug() << "/// SWAPPING CLIPS";
2617             if (m_allClips[cid1]->getPosition() > m_allClips[cid2]->getPosition()) {
2618                 // Incorrecty detected revert mix
2619                 std::swap(cid1, cid2);
2620             }
2621         }
2622         if (m_allClips[cid1]->getPosition() > m_allClips[cid2]->getPosition() ||
2623             (m_allClips[cid1]->getPosition() + m_allClips[cid1]->getPlaytime()) > (m_allClips[cid2]->getPosition() + m_allClips[cid2]->getPlaytime())) {
2624             // Impossible mix, remove
2625             QScopedPointer<Mlt::Field> field(m_track->field());
2626             field->lock();
2627             field->disconnect_service(*t);
2628             field->unlock();
2629             return false;
2630         }
2631     }
2632     // Ensure in/out points are in sync with the clips
2633     int clipIn = m_allClips[cid2]->getPosition();
2634     int clipOut = m_allClips[cid1]->getPosition() + m_allClips[cid1]->getPlaytime();
2635     if (in != clipIn || out != clipOut) {
2636         t->set_in_and_out(clipIn, clipOut);
2637     }
2638     QString assetId(t->get("kdenlive_id"));
2639     if (assetId.isEmpty()) {
2640         assetId = QString(t->get("mlt_service"));
2641     }
2642     std::unique_ptr<Mlt::Transition> tr(t);
2643     QDomElement xml = TransitionsRepository::get()->getXml(assetId);
2644     // Paste parameters from existing mix
2645     // std::unique_ptr<Mlt::Properties> sourceProperties(t);
2646     QStringList sourceProps;
2647     for (int i = 0; i < tr->count(); i++) {
2648         sourceProps << tr->get_name(i);
2649     }
2650     QDomNodeList params = xml.elementsByTagName(QStringLiteral("parameter"));
2651     for (int i = 0; i < params.count(); ++i) {
2652         QDomElement currentParameter = params.item(i).toElement();
2653         QString paramName = currentParameter.attribute(QStringLiteral("name"));
2654         if (!sourceProps.contains(paramName)) {
2655             continue;
2656         }
2657         QString paramValue = tr->get(paramName.toUtf8().constData());
2658         currentParameter.setAttribute(QStringLiteral("value"), paramValue);
2659     }
2660     QUuid timelineUuid;
2661     if (auto ptr = m_parent.lock()) {
2662         timelineUuid = ptr->uuid();
2663     }
2664     std::shared_ptr<AssetParameterModel> asset(
2665         new AssetParameterModel(std::move(tr), xml, assetId, ObjectId(KdenliveObjectType::TimelineMix, cid2, timelineUuid), QString()));
2666     m_sameCompositions[cid2] = asset;
2667     m_mixList.insert(cid1, cid2);
2668     int mixDuration = t->get_length() - 1;
2669     int mixCutPos = qMin(t->get_int("kdenlive:mixcut"), mixDuration);
2670     setMixDuration(cid2, mixDuration, mixCutPos);
2671     return true;
2672 }
2673 
2674 const std::shared_ptr<AssetParameterModel> TrackModel::mixModel(int cid)
2675 {
2676     if (m_sameCompositions.count(cid) > 0) {
2677         return m_sameCompositions[cid];
2678     }
2679     return nullptr;
2680 }
2681 
2682 bool TrackModel::reAssignEndMix(int currentId, int newId)
2683 {
2684     Q_ASSERT(m_mixList.contains(currentId));
2685     int mixedClip = m_mixList.value(currentId);
2686     m_mixList.remove(currentId);
2687     m_mixList.insert(newId, mixedClip);
2688     return true;
2689 }
2690 
2691 std::pair<int, int> TrackModel::getMixTracks(int cid) const
2692 {
2693     Q_ASSERT(m_sameCompositions.count(cid) > 0);
2694     int a_track = m_sameCompositions.at(cid)->getAsset()->get_int("a_track");
2695     int b_track = m_sameCompositions.at(cid)->getAsset()->get_int("b_track");
2696     return {a_track, b_track};
2697 }
2698 
2699 std::pair<QString, QVector<QPair<QString, QVariant>>> TrackModel::getMixParams(int cid)
2700 {
2701     Q_ASSERT(m_sameCompositions.count(cid) > 0);
2702     const QString assetId = m_sameCompositions[cid]->getAssetId();
2703     QVector<QPair<QString, QVariant>> params = m_sameCompositions[cid]->getAllParameters();
2704     return {assetId, params};
2705 }
2706 
2707 void TrackModel::switchMix(int cid, const QString &composition, Fun &undo, Fun &redo)
2708 {
2709     // First remove existing mix
2710     // lock MLT playlist so that we don't end up with invalid frames in monitor
2711     const QString currentAsset = m_sameCompositions[cid]->getAssetId();
2712     QVector<QPair<QString, QVariant>> allParams = m_sameCompositions[cid]->getAllParameters();
2713     std::pair<int, int> originTracks = getMixTracks(cid);
2714     // Check if mix should be reversed
2715     bool reverse = m_allClips[cid]->getSubPlaylistIndex() == 0;
2716     Fun local_redo = [this, cid, composition, reverse]() {
2717         m_playlists[0].lock();
2718         m_playlists[1].lock();
2719         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[cid]->getAsset());
2720         int in = transition.get_in();
2721         int out = transition.get_out();
2722         int mixCutPos = transition.get_int("kdenlive:mixcut");
2723         QScopedPointer<Mlt::Field> field(m_track->field());
2724         field->lock();
2725         field->disconnect_service(transition);
2726         field->unlock();
2727         m_sameCompositions.erase(cid);
2728         if (auto ptr = m_parent.lock()) {
2729             std::unique_ptr<Mlt::Transition> t = TransitionsRepository::get()->getTransition(composition);
2730             t->set_in_and_out(in, out);
2731             if (reverse) {
2732                 t->set_tracks(1, 0);
2733                 m_track->plant_transition(*t.get(), 1, 0);
2734             } else {
2735                 t->set_tracks(0, 1);
2736                 m_track->plant_transition(*t.get(), 0, 1);
2737             }
2738             t->set("kdenlive:mixcut", mixCutPos);
2739             QDomElement xml = TransitionsRepository::get()->getXml(composition);
2740             std::shared_ptr<AssetParameterModel> asset(
2741                 new AssetParameterModel(std::move(t), xml, composition, ObjectId(KdenliveObjectType::TimelineMix, cid, ptr->uuid()), QString()));
2742             m_sameCompositions[cid] = asset;
2743         }
2744         m_playlists[0].unlock();
2745         m_playlists[1].unlock();
2746         return true;
2747     };
2748     Fun local_undo = [this, cid, currentAsset, allParams, originTracks]() {
2749         m_playlists[0].lock();
2750         m_playlists[1].lock();
2751         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[cid]->getAsset());
2752         int in = transition.get_in();
2753         int out = transition.get_out();
2754         QScopedPointer<Mlt::Field> field(m_track->field());
2755         field->lock();
2756         field->disconnect_service(transition);
2757         field->unlock();
2758         m_sameCompositions.erase(cid);
2759         if (auto ptr = m_parent.lock()) {
2760             std::unique_ptr<Mlt::Transition> t = TransitionsRepository::get()->getTransition(currentAsset);
2761             t->set_in_and_out(in, out);
2762             t->set_tracks(originTracks.first, originTracks.second);
2763             m_track->plant_transition(*t.get(), originTracks.first, originTracks.second);
2764             QDomElement xml = TransitionsRepository::get()->getXml(currentAsset);
2765             QDomNodeList xmlParams = xml.elementsByTagName(QStringLiteral("parameter"));
2766             for (int i = 0; i < xmlParams.count(); ++i) {
2767                 QDomElement currentParameter = xmlParams.item(i).toElement();
2768                 QString paramName = currentParameter.attribute(QStringLiteral("name"));
2769                 for (const auto &p : qAsConst(allParams)) {
2770                     if (p.first == paramName) {
2771                         currentParameter.setAttribute(QStringLiteral("value"), p.second.toString());
2772                         break;
2773                     }
2774                 }
2775             }
2776             std::shared_ptr<AssetParameterModel> asset(
2777                 new AssetParameterModel(std::move(t), xml, currentAsset, ObjectId(KdenliveObjectType::TimelineMix, cid, ptr->uuid()), QString()));
2778             m_sameCompositions[cid] = asset;
2779         }
2780         m_playlists[0].unlock();
2781         m_playlists[1].unlock();
2782         return true;
2783     };
2784     UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
2785 }
2786 
2787 void TrackModel::reverseCompositionXml(const QString &composition, QDomElement xml)
2788 {
2789     if (composition == QLatin1String("luma") || composition == QLatin1String("dissolve") || composition == QLatin1String("mix")) {
2790         Xml::setXmlParameter(xml, QStringLiteral("reverse"), QStringLiteral("1"));
2791     } else if (composition == QLatin1String("composite")) {
2792         Xml::setXmlParameter(xml, QStringLiteral("invert"), QStringLiteral("1"));
2793     } else if (composition == QLatin1String("wipe")) {
2794         Xml::setXmlParameter(xml, QStringLiteral("geometry"), QStringLiteral("0=0% 0% 100% 100% 100%;-1=0% 0% 100% 100% 0%"));
2795     } else if (composition == QLatin1String("slide")) {
2796         Xml::setXmlParameter(xml, QStringLiteral("rect"), QStringLiteral("0=0% 0% 100% 100% 100%;-1=100% 0% 100% 100% 100%"));
2797     }
2798 }
2799 
2800 void TrackModel::updateCompositionDirection(Mlt::Transition &transition, bool reverse)
2801 {
2802     if (reverse) {
2803         transition.set_tracks(1, 0);
2804         m_track->plant_transition(transition, 1, 0);
2805     } else {
2806         transition.set_tracks(0, 1);
2807         m_track->plant_transition(transition, 0, 1);
2808     }
2809 }
2810 
2811 bool TrackModel::mixIsReversed(int cid) const
2812 {
2813     if (m_sameCompositions.count(cid) > 0) {
2814         // There is a mix at clip start, adjust direction
2815         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions.at(cid)->getAsset());
2816         if (isAudioTrack()) {
2817             qDebug() << "::: CHKING TRANSITION ON AUDIO TRACK: " << transition.get_int("reverse");
2818             return transition.get_int("reverse") == 1;
2819         }
2820         qDebug() << "::: CHKING TRANSITION ON video TRACK: ";
2821         return (transition.get_a_track() == 1 && transition.get_b_track() == 0);
2822     }
2823     return false;
2824 }
2825 
2826 QVariantList TrackModel::stackZones() const
2827 {
2828     return m_effectStack->getEffectZones();
2829 }
2830 
2831 bool TrackModel::hasClipStart(int pos)
2832 {
2833     for (auto &m_playlist : m_playlists) {
2834         if (m_playlist.is_blank_at(pos)) {
2835             continue;
2836         }
2837         if (pos == 0 || m_playlist.get_clip_index_at(pos) != m_playlist.get_clip_index_at(pos - 1)) {
2838             return true;
2839         }
2840     }
2841     return false;
2842 }
2843 
2844 QByteArray TrackModel::trackHash()
2845 {
2846     QByteArray fileData;
2847     // Parse clips
2848     for (auto &clip : m_allClips) {
2849         fileData.append(clip.second->clipHash().toUtf8());
2850     }
2851     // Parse mixes
2852     for (auto &sameComposition : m_sameCompositions) {
2853         Mlt::Transition *tr = static_cast<Mlt::Transition *>(sameComposition.second->getAsset());
2854         QString mixData = QString("%1 %2 %3").arg(QString::number(tr->get_in()), QString::number(tr->get_out()), sameComposition.second->getAssetId());
2855         fileData.append(mixData.toLatin1());
2856     }
2857     return fileData;
2858 }