File indexing completed on 2024-05-12 08:54:45

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 ix = 0;
1888         int moveId = m_mixList.value(clipIds.second, -1);
1889         while (moveId > -1) {
1890             int current = m_allClips[moveId]->getSubPlaylistIndex();
1891             rearrangedPlaylists.insert(moveId, current);
1892             QVector<QPair<QString, QVariant>> params = m_sameCompositions.at(moveId)->getAllParameters();
1893             mixParameters.insert(moveId, params);
1894             if (hasEndMix(moveId)) {
1895                 moveId = m_mixList.value(moveId, -1);
1896             } else {
1897                 break;
1898             }
1899             ix++;
1900         }
1901         rearrange_playlists = [this, map = rearrangedPlaylists]() {
1902             // First, remove all clips on playlist 0
1903             QMapIterator<int, int> i(map);
1904             while (i.hasNext()) {
1905                 i.next();
1906                 if (i.value() == 0) {
1907                     int target_clip = m_playlists[0].get_clip_index_at(m_allClips[i.key()]->getPosition());
1908                     std::unique_ptr<Mlt::Producer> prod(m_playlists[0].replace_with_blank(target_clip));
1909                 }
1910             }
1911             m_playlists[0].consolidate_blanks();
1912             // Then move all clips from playlist 1 to playlist 0
1913             i.toFront();
1914             if (auto ptr = m_parent.lock()) {
1915                 while (i.hasNext()) {
1916                     i.next();
1917                     if (i.value() == 1) {
1918                         // Remove
1919                         int pos = m_allClips[i.key()]->getPosition();
1920                         int target_clip = m_playlists[1].get_clip_index_at(pos);
1921                         std::unique_ptr<Mlt::Producer> prod(m_playlists[1].replace_with_blank(target_clip));
1922                         prod.reset();
1923                         // Replug
1924                         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(i.key());
1925                         clip->setSubPlaylistIndex(0, m_id);
1926                         int index = m_playlists[0].insert_at(pos, *clip, 1);
1927                         m_playlists[0].consolidate_blanks();
1928                         if (index == -1) {
1929                             // Something went wrong, abort
1930                             m_playlists[1].insert_at(pos, *clip, 1);
1931                             m_playlists[1].consolidate_blanks();
1932                             return false;
1933                         }
1934                     }
1935                 }
1936                 m_playlists[1].consolidate_blanks();
1937                 // Finally replug playlist 0 clips in playlist 1 and fix transition direction
1938                 i.toFront();
1939                 while (i.hasNext()) {
1940                     i.next();
1941                     if (i.value() == 0) {
1942                         int pos = m_allClips[i.key()]->getPosition();
1943                         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(i.key());
1944                         clip->setSubPlaylistIndex(1, m_id);
1945                         int index = m_playlists[1].insert_at(pos, *clip, 1);
1946                         m_playlists[1].consolidate_blanks();
1947                         if (index == -1) {
1948                             // Something went wrong, abort
1949                             m_playlists[0].insert_at(pos, *clip, 1);
1950                             m_playlists[0].consolidate_blanks();
1951                             return false;
1952                         }
1953                     }
1954                     std::unique_ptr<Mlt::Field> field(m_track->field());
1955                     field->block();
1956                     if (m_sameCompositions.count(i.key()) > 0) {
1957                         // There is a mix at clip start, adjust direction
1958                         Mlt::Properties *props = m_sameCompositions[i.key()]->getAsset();
1959                         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[i.key()]->getAsset());
1960                         bool reverse = i.value() == 1;
1961                         const QString assetName = m_sameCompositions[i.key()]->getAssetId();
1962                         const ObjectId ownerId = m_sameCompositions[i.key()]->getOwnerId();
1963                         field->disconnect_service(transition);
1964                         std::unique_ptr<Mlt::Transition> newTrans = TransitionsRepository::get()->getTransition(m_sameCompositions[i.key()]->getAssetId());
1965                         newTrans->inherit(*props);
1966                         updateCompositionDirection(*newTrans.get(), reverse);
1967                         m_sameCompositions.erase(i.key());
1968                         QDomElement xml = TransitionsRepository::get()->getXml(assetName);
1969                         std::shared_ptr<AssetParameterModel> asset(new AssetParameterModel(std::move(newTrans), xml, assetName, ownerId, QString()));
1970                         m_sameCompositions[i.key()] = asset;
1971                     }
1972                     field->unblock();
1973                 }
1974                 return true;
1975             } else {
1976                 return false;
1977             }
1978         };
1979         rearrange_playlists_undo = [this, map = rearrangedPlaylists, mixParameters]() {
1980             // First, remove all clips on playlist 1
1981             QMapIterator<int, int> i(map);
1982             while (i.hasNext()) {
1983                 i.next();
1984                 if (i.value() == 0) {
1985                     int target_clip = m_playlists[1].get_clip_index_at(m_allClips[i.key()]->getPosition());
1986                     std::unique_ptr<Mlt::Producer> prod(m_playlists[1].replace_with_blank(target_clip));
1987                 }
1988                 m_playlists[1].consolidate_blanks();
1989             }
1990             // Then move all clips from playlist 0 to playlist 1
1991             i.toFront();
1992             if (auto ptr = m_parent.lock()) {
1993                 while (i.hasNext()) {
1994                     i.next();
1995                     if (i.value() == 1) {
1996                         // Remove
1997                         int pos = m_allClips[i.key()]->getPosition();
1998                         int target_clip = m_playlists[0].get_clip_index_at(pos);
1999                         std::unique_ptr<Mlt::Producer> prod(m_playlists[0].replace_with_blank(target_clip));
2000                         // Replug
2001                         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(i.key());
2002                         clip->setSubPlaylistIndex(1, m_id);
2003                         int index = m_playlists[1].insert_at(pos, *clip, 1);
2004                         m_playlists[1].consolidate_blanks();
2005                         if (index == -1) {
2006                             // Something went wrong, abort
2007                             m_playlists[0].insert_at(pos, *clip, 1);
2008                             m_playlists[0].consolidate_blanks();
2009                             return false;
2010                         }
2011                     }
2012                     m_playlists[0].consolidate_blanks();
2013                 }
2014                 // Finally replug playlist 1 clips in playlist 0 and fix transition direction
2015                 i.toFront();
2016                 while (i.hasNext()) {
2017                     i.next();
2018                     if (i.value() == 0) {
2019                         int pos = m_allClips[i.key()]->getPosition();
2020                         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(i.key());
2021                         clip->setSubPlaylistIndex(0, m_id);
2022                         int index = m_playlists[0].insert_at(pos, *clip, 1);
2023                         m_playlists[0].consolidate_blanks();
2024                         if (index == -1) {
2025                             // Something went wrong, abort
2026                             m_playlists[1].insert_at(pos, *clip, 1);
2027                             m_playlists[1].consolidate_blanks();
2028                             return false;
2029                         }
2030                     }
2031                     std::unique_ptr<Mlt::Field> field(m_track->field());
2032                     field->block();
2033                     if (m_sameCompositions.count(i.key()) > 0) {
2034                         // There is a mix at clip start, adjust direction
2035                         Mlt::Properties *props = m_sameCompositions[i.key()]->getAsset();
2036                         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[i.key()]->getAsset());
2037                         bool reverse = i.value() == 0;
2038                         const QString assetName = m_sameCompositions[i.key()]->getAssetId();
2039                         const ObjectId ownerId = m_sameCompositions[i.key()]->getOwnerId();
2040                         field->disconnect_service(transition);
2041                         std::unique_ptr<Mlt::Transition> newTrans = TransitionsRepository::get()->getTransition(m_sameCompositions[i.key()]->getAssetId());
2042                         newTrans->inherit(*props);
2043                         updateCompositionDirection(*newTrans.get(), reverse);
2044                         m_sameCompositions.erase(i.key());
2045                         QDomElement xml = TransitionsRepository::get()->getXml(assetName);
2046                         std::shared_ptr<AssetParameterModel> asset(new AssetParameterModel(std::move(newTrans), xml, assetName, ownerId, QString()));
2047                         m_sameCompositions[i.key()] = asset;
2048                     }
2049                     field->unblock();
2050                 }
2051                 return true;
2052             } else {
2053                 return false;
2054             }
2055         };
2056     }
2057     // Create mix compositing
2058     Fun build_mix = [clipIds, mixPosition, mixDurations, dest_track, secondClipCut, mixId, this]() {
2059         if (auto ptr = m_parent.lock()) {
2060             std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(clipIds.second));
2061             movedClip->setMixDuration(mixDurations.first + mixDurations.second, secondClipCut);
2062             // Insert mix transition
2063             QString assetName;
2064             std::unique_ptr<Mlt::Transition> t;
2065             QDomElement xml;
2066             if (isAudioTrack()) {
2067                 // Mix is the only audio transition
2068                 t = TransitionsRepository::get()->getTransition(QStringLiteral("mix"));
2069                 t->set_in_and_out(mixPosition, mixPosition + mixDurations.first + mixDurations.second);
2070                 t->set("kdenlive:mixcut", secondClipCut);
2071                 t->set("start", -1);
2072                 t->set("accepts_blanks", 1);
2073                 assetName = QStringLiteral("mix");
2074                 xml = TransitionsRepository::get()->getXml(assetName);
2075                 if (dest_track == 0) {
2076                     t->set("reverse", 1);
2077                     Xml::setXmlParameter(xml, QStringLiteral("reverse"), QStringLiteral("1"));
2078                 }
2079                 m_track->plant_transition(*t.get(), 0, 1);
2080             } else {
2081                 assetName = mixId.isEmpty() || mixId == QLatin1String("mix") ? QStringLiteral("luma") : mixId;
2082                 t = TransitionsRepository::get()->getTransition(assetName);
2083                 t->set_in_and_out(mixPosition, mixPosition + mixDurations.first + mixDurations.second);
2084                 xml = TransitionsRepository::get()->getXml(assetName);
2085                 t->set("kdenlive:mixcut", secondClipCut);
2086                 t->set("kdenlive_id", assetName.toUtf8().constData());
2087                 if (dest_track == 0) {
2088                     t->set_tracks(1, 0);
2089                     m_track->plant_transition(*t.get(), 1, 0);
2090                 } else {
2091                     t->set_tracks(0, 1);
2092                     m_track->plant_transition(*t.get(), 0, 1);
2093                 }
2094             }
2095             std::shared_ptr<AssetParameterModel> asset(
2096                 new AssetParameterModel(std::move(t), xml, assetName, ObjectId(KdenliveObjectType::TimelineMix, clipIds.second, ptr->uuid()), QString()));
2097             m_sameCompositions[clipIds.second] = asset;
2098             m_mixList.insert(clipIds.first, clipIds.second);
2099         }
2100         return true;
2101     };
2102 
2103     Fun destroy_mix = [clipIds, this]() {
2104         if (auto ptr = m_parent.lock()) {
2105             if (m_sameCompositions.count(clipIds.second) == 0) {
2106                 // Mix was already deleted
2107                 if (m_mixList.contains(clipIds.first)) {
2108                     m_mixList.remove(clipIds.first);
2109                 }
2110                 return true;
2111             }
2112             // Clear asset panel if mix was selected
2113             if (ptr->m_selectedMix == clipIds.second) {
2114                 ptr->requestClearAssetView(clipIds.second);
2115             }
2116             Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[clipIds.second]->getAsset());
2117             std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(clipIds.second));
2118             movedClip->setMixDuration(0);
2119             QModelIndex ix = ptr->makeClipIndexFromID(clipIds.second);
2120             Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::StartRole, TimelineModel::MixRole, TimelineModel::MixCutRole});
2121             QScopedPointer<Mlt::Field> field(m_track->field());
2122             field->lock();
2123             field->disconnect_service(transition);
2124             field->unlock();
2125             m_sameCompositions.erase(clipIds.second);
2126             m_mixList.remove(clipIds.first);
2127         }
2128         return true;
2129     };
2130     // lock MLT playlist so that we don't end up with invalid frames in monitor
2131     auto operation = requestClipDeletion_lambda(clipIds.second, updateView, finalMove, groupMove, false);
2132     bool res = operation();
2133     if (res) {
2134         Fun replay = [this, clipIds, dest_track, firstClipPos, secondClipDuration, mixPosition, mixDurations, build_mix, secondClipPos, clipHasEndMix,
2135                       updateView, finalMove, groupMove, rearrange_playlists]() {
2136             if (auto ptr = m_parent.lock()) {
2137                 ptr->getClipPtr(clipIds.second)->setSubPlaylistIndex(dest_track, m_id);
2138             }
2139             bool result = rearrange_playlists();
2140             auto op = requestClipInsertion_lambda(clipIds.second, secondClipPos, updateView, finalMove, groupMove);
2141             result = result && op();
2142             if (result) {
2143                 build_mix();
2144                 std::function<bool(void)> local_undo = []() { return true; };
2145                 std::function<bool(void)> local_redo = []() { return true; };
2146                 if (auto ptr = m_parent.lock()) {
2147                     result = ptr->getClipPtr(clipIds.second)
2148                                  ->requestResize(secondClipPos + secondClipDuration - mixPosition, false, local_undo, local_redo, true, clipHasEndMix);
2149                     result = result && ptr->getClipPtr(clipIds.first)
2150                                            ->requestResize(mixPosition + mixDurations.first + mixDurations.second - firstClipPos, true, local_undo, local_redo,
2151                                                            true, true);
2152                     QModelIndex ix = ptr->makeClipIndexFromID(clipIds.second);
2153                     Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::StartRole, TimelineModel::MixRole, TimelineModel::MixCutRole});
2154                 }
2155             }
2156             return result;
2157         };
2158 
2159         Fun reverse = [this, clipIds, source_track, secondClipDuration, firstClipDuration, destroy_mix, secondClipPos, updateView, finalMove, groupMove,
2160                        operation, rearrange_playlists_undo]() {
2161             destroy_mix();
2162             std::function<bool(void)> local_undo = []() { return true; };
2163             std::function<bool(void)> local_redo = []() { return true; };
2164             if (auto ptr = m_parent.lock()) {
2165                 ptr->getClipPtr(clipIds.second)->requestResize(secondClipDuration, false, local_undo, local_redo, false);
2166                 ptr->getClipPtr(clipIds.first)->requestResize(firstClipDuration, true, local_undo, local_redo, false);
2167             }
2168             bool result = operation();
2169             if (auto ptr = m_parent.lock()) {
2170                 ptr->getClipPtr(clipIds.second)->setSubPlaylistIndex(source_track, m_id);
2171             }
2172             result = result && rearrange_playlists_undo();
2173             auto op = requestClipInsertion_lambda(clipIds.second, secondClipPos, updateView, finalMove, groupMove);
2174             result = result && op();
2175             return result;
2176         };
2177         res = replay();
2178         if (res) {
2179             PUSH_LAMBDA(replay, operation);
2180             UPDATE_UNDO_REDO(operation, reverse, undo, redo);
2181         } else {
2182             qDebug() << "=============\nSECOND INSERT FAILED\n\n=================";
2183             reverse();
2184         }
2185     } else {
2186         qDebug() << "=== CLIP DELETION FAILED";
2187     }
2188     return res;
2189 }
2190 
2191 std::pair<MixInfo, MixInfo> TrackModel::getMixInfo(int clipId) const
2192 {
2193     std::pair<MixInfo, MixInfo> result;
2194     MixInfo startMix;
2195     MixInfo endMix;
2196     if (m_sameCompositions.count(clipId) > 0) {
2197         // There is a mix at clip start
2198         startMix.firstClipId = m_mixList.key(clipId, -1);
2199         startMix.secondClipId = clipId;
2200         if (auto ptr = m_parent.lock()) {
2201             if (ptr->isClip(startMix.firstClipId)) {
2202                 std::shared_ptr<ClipModel> clip1 = ptr->getClipPtr(startMix.firstClipId);
2203                 std::shared_ptr<ClipModel> clip2 = ptr->getClipPtr(startMix.secondClipId);
2204                 startMix.firstClipInOut.first = clip1->getPosition();
2205                 startMix.firstClipInOut.second = startMix.firstClipInOut.first + clip1->getPlaytime();
2206                 startMix.secondClipInOut.first = clip2->getPosition();
2207                 startMix.secondClipInOut.second = startMix.secondClipInOut.first + clip2->getPlaytime();
2208                 startMix.mixOffset = clip2->getMixCutPosition();
2209             } else {
2210                 // Clip was deleted
2211                 startMix.firstClipId = -1;
2212             }
2213         }
2214     } else {
2215         startMix.firstClipId = -1;
2216         startMix.secondClipId = -1;
2217     }
2218     int secondClip = m_mixList.value(clipId, -1);
2219     if (secondClip > -1) {
2220         // There is a mix at clip end
2221         endMix.firstClipId = clipId;
2222         endMix.secondClipId = secondClip;
2223         if (auto ptr = m_parent.lock()) {
2224             if (ptr->isClip(secondClip)) {
2225                 std::shared_ptr<ClipModel> clip1 = ptr->getClipPtr(endMix.firstClipId);
2226                 std::shared_ptr<ClipModel> clip2 = ptr->getClipPtr(endMix.secondClipId);
2227                 endMix.firstClipInOut.first = clip1->getPosition();
2228                 endMix.firstClipInOut.second = endMix.firstClipInOut.first + clip1->getPlaytime();
2229                 endMix.secondClipInOut.first = clip2->getPosition();
2230                 endMix.secondClipInOut.second = endMix.secondClipInOut.first + clip2->getPlaytime();
2231             } else {
2232                 // Clip was deleted
2233                 endMix.firstClipId = -1;
2234             }
2235         }
2236     } else {
2237         endMix.firstClipId = -1;
2238         endMix.secondClipId = -1;
2239     }
2240     result = {startMix, endMix};
2241     return result;
2242 }
2243 
2244 bool TrackModel::deleteMix(int clipId, bool final, bool notify)
2245 {
2246     Q_ASSERT(m_sameCompositions.count(clipId) > 0);
2247     if (auto ptr = m_parent.lock()) {
2248         if (notify) {
2249             std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(clipId));
2250             movedClip->setMixDuration(final ? 0 : 1);
2251             QModelIndex ix = ptr->makeClipIndexFromID(clipId);
2252             Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::StartRole, TimelineModel::MixRole, TimelineModel::MixCutRole});
2253         }
2254         if (final) {
2255             Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[clipId]->getAsset());
2256             QScopedPointer<Mlt::Field> field(m_track->field());
2257             field->lock();
2258             field->disconnect_service(transition);
2259             field->unlock();
2260             m_sameCompositions.erase(clipId);
2261             int firstClip = m_mixList.key(clipId, -1);
2262             if (firstClip > -1) {
2263                 m_mixList.remove(firstClip);
2264             }
2265         }
2266         return true;
2267     }
2268     return false;
2269 }
2270 
2271 bool TrackModel::createMix(MixInfo info, std::pair<QString, QVector<QPair<QString, QVariant>>> params, std::pair<int, int> tracks, bool finalMove)
2272 {
2273     if (m_sameCompositions.count(info.secondClipId) > 0) {
2274         // Clip already has a mix
2275         Q_ASSERT(false);
2276         return false;
2277     }
2278     if (auto ptr = m_parent.lock()) {
2279         // Insert mix transition
2280         std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(info.secondClipId));
2281         int in = movedClip->getPosition();
2282         // int out = in + info.firstClipInOut.second - info.secondClipInOut.first;
2283         int duration = info.firstClipInOut.second - info.secondClipInOut.first;
2284         int out = in + duration;
2285         movedClip->setMixDuration(duration, info.mixOffset);
2286         std::unique_ptr<Mlt::Transition> t;
2287         const QString assetId = params.first;
2288         t = std::make_unique<Mlt::Transition>(pCore->getProjectProfile().get_profile(), assetId.toUtf8().constData());
2289         t->set_in_and_out(in, out);
2290         t->set("kdenlive:mixcut", info.mixOffset);
2291         t->set("kdenlive_id", assetId.toUtf8().constData());
2292         t->set_tracks(tracks.first, tracks.second);
2293         m_track->plant_transition(*t.get(), tracks.first, tracks.second);
2294         QDomElement xml = TransitionsRepository::get()->getXml(assetId);
2295         QDomNodeList xmlParams = xml.elementsByTagName(QStringLiteral("parameter"));
2296         for (int i = 0; i < xmlParams.count(); ++i) {
2297             QDomElement currentParameter = xmlParams.item(i).toElement();
2298             QString paramName = currentParameter.attribute(QStringLiteral("name"));
2299             for (const auto &p : qAsConst(params.second)) {
2300                 if (p.first == paramName) {
2301                     currentParameter.setAttribute(QStringLiteral("value"), p.second.toString());
2302                     break;
2303                 }
2304             }
2305         }
2306         std::shared_ptr<AssetParameterModel> asset(
2307             new AssetParameterModel(std::move(t), xml, assetId, ObjectId(KdenliveObjectType::TimelineMix, info.secondClipId, ptr->uuid()), QString()));
2308         m_sameCompositions[info.secondClipId] = asset;
2309         m_mixList.insert(info.firstClipId, info.secondClipId);
2310         if (finalMove) {
2311             QModelIndex ix2 = ptr->makeClipIndexFromID(info.secondClipId);
2312             Q_EMIT ptr->dataChanged(ix2, ix2, {TimelineModel::MixRole, TimelineModel::MixCutRole});
2313         }
2314         return true;
2315     }
2316     qDebug() << "== COULD NOT PLANT MIX; TRACK UNAVAILABLE";
2317     return false;
2318 }
2319 
2320 bool TrackModel::createMix(MixInfo info, bool isAudio)
2321 {
2322     if (m_sameCompositions.count(info.secondClipId) > 0) {
2323         return false;
2324     }
2325     if (auto ptr = m_parent.lock()) {
2326         // Insert mix transition
2327         std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(info.secondClipId));
2328         int in = movedClip->getPosition();
2329         // int out = in + info.firstClipInOut.second - info.secondClipInOut.first;
2330         int out = in + movedClip->getMixDuration();
2331         movedClip->setMixDuration(out - in);
2332         bool reverse = movedClip->getSubPlaylistIndex() == 0;
2333         QString assetName;
2334         std::unique_ptr<Mlt::Transition> t;
2335         if (isAudio) {
2336             t = std::make_unique<Mlt::Transition>(pCore->getProjectProfile().get_profile(), "mix");
2337             t->set_in_and_out(in, out);
2338             t->set("start", -1);
2339             t->set("accepts_blanks", 1);
2340             if (reverse) {
2341                 t->set("reverse", 1);
2342             }
2343             m_track->plant_transition(*t.get(), 0, 1);
2344             assetName = QStringLiteral("mix");
2345         } else {
2346             t = std::make_unique<Mlt::Transition>(pCore->getProjectProfile().get_profile(), "luma");
2347             t->set_in_and_out(in, out);
2348             t->set("kdenlive_id", "luma");
2349             if (reverse) {
2350                 t->set_tracks(1, 0);
2351                 m_track->plant_transition(*t.get(), 1, 0);
2352             } else {
2353                 t->set_tracks(0, 1);
2354                 m_track->plant_transition(*t.get(), 0, 1);
2355             }
2356             /*if (reverse) {
2357                 t->set("reverse", 1);
2358             }
2359             m_track->plant_transition(*t.get(), 0, 1);*/
2360             assetName = QStringLiteral("luma");
2361         }
2362         QDomElement xml = TransitionsRepository::get()->getXml(assetName);
2363         std::shared_ptr<AssetParameterModel> asset(
2364             new AssetParameterModel(std::move(t), xml, assetName, ObjectId(KdenliveObjectType::TimelineMix, info.secondClipId, ptr->uuid()), QString()));
2365         m_sameCompositions[info.secondClipId] = asset;
2366         m_mixList.insert(info.firstClipId, info.secondClipId);
2367         return true;
2368     }
2369     return false;
2370 }
2371 
2372 bool TrackModel::createMix(std::pair<int, int> clipIds, std::pair<int, int> mixData)
2373 {
2374     if (m_sameCompositions.count(clipIds.second) > 0) {
2375         return false;
2376     }
2377     if (auto ptr = m_parent.lock()) {
2378         std::shared_ptr<ClipModel> movedClip(ptr->getClipPtr(clipIds.second));
2379         movedClip->setMixDuration(mixData.second);
2380         QModelIndex ix = ptr->makeClipIndexFromID(clipIds.second);
2381         Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::MixRole, TimelineModel::MixCutRole});
2382         bool reverse = movedClip->getSubPlaylistIndex() == 0;
2383         // Insert mix transition
2384         QString assetName;
2385         std::unique_ptr<Mlt::Transition> t;
2386         if (isAudioTrack()) {
2387             t = std::make_unique<Mlt::Transition>(pCore->getProjectProfile().get_profile(), "mix");
2388             t->set_in_and_out(mixData.first, mixData.first + mixData.second);
2389             t->set("start", -1);
2390             t->set("accepts_blanks", 1);
2391             if (reverse) {
2392                 t->set("reverse", 1);
2393             }
2394             m_track->plant_transition(*t.get(), 0, 1);
2395             assetName = QStringLiteral("mix");
2396         } else {
2397             t = std::make_unique<Mlt::Transition>(pCore->getProjectProfile().get_profile(), "luma");
2398             t->set("kdenlive_id", "luma");
2399             t->set_in_and_out(mixData.first, mixData.first + mixData.second);
2400             if (reverse) {
2401                 t->set_tracks(1, 0);
2402                 m_track->plant_transition(*t.get(), 1, 0);
2403             } else {
2404                 t->set_tracks(0, 1);
2405                 m_track->plant_transition(*t.get(), 0, 1);
2406             }
2407             /*if (reverse) {
2408                 t->set("reverse", 1);
2409             }
2410             m_track->plant_transition(*t.get(), 0, 1);*/
2411             assetName = QStringLiteral("luma");
2412         }
2413         QDomElement xml = TransitionsRepository::get()->getXml(assetName);
2414         std::shared_ptr<AssetParameterModel> asset(
2415             new AssetParameterModel(std::move(t), xml, assetName, ObjectId(KdenliveObjectType::TimelineMix, clipIds.second, ptr->uuid()), QString()));
2416         m_sameCompositions[clipIds.second] = asset;
2417         m_mixList.insert(clipIds.first, clipIds.second);
2418         return true;
2419     }
2420     return false;
2421 }
2422 
2423 void TrackModel::setMixDuration(int cid, int mixDuration, int mixCut)
2424 {
2425     m_allClips[cid]->setMixDuration(mixDuration, mixCut);
2426     m_sameCompositions[cid]->getAsset()->set("kdenlive:mixcut", mixCut);
2427     int in = m_allClips[cid]->getPosition();
2428     int out = in + mixDuration;
2429     Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[cid]->getAsset());
2430     transition.set_in_and_out(in, out);
2431     Q_EMIT m_sameCompositions[cid]->dataChanged(QModelIndex(), QModelIndex(), {AssetParameterModel::ParentDurationRole});
2432 }
2433 
2434 int TrackModel::getMixDuration(int cid) const
2435 {
2436     Q_ASSERT(m_sameCompositions.count(cid) > 0);
2437     Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions.at(cid)->getAsset());
2438     return transition.get_length() - 1;
2439 }
2440 
2441 void TrackModel::removeMix(const MixInfo &info)
2442 {
2443     Q_ASSERT(m_sameCompositions.count(info.secondClipId) > 0);
2444     Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[info.secondClipId]->getAsset());
2445     QScopedPointer<Mlt::Field> field(m_track->field());
2446     field->lock();
2447     field->disconnect_service(transition);
2448     field->unlock();
2449     m_sameCompositions.erase(info.secondClipId);
2450     m_mixList.remove(info.firstClipId);
2451 }
2452 
2453 void TrackModel::syncronizeMixes(bool finalMove)
2454 {
2455     QList<int> toDelete;
2456     for (const auto &n : m_sameCompositions) {
2457         // qDebug() << "Key:[" << n.first << "] Value:[" << n.second << "]\n";
2458         int secondClipId = n.first;
2459         int firstClip = m_mixList.key(secondClipId, -1);
2460         Q_ASSERT(firstClip > -1);
2461         if (m_allClips.find(firstClip) == m_allClips.end() || m_allClips.find(secondClipId) == m_allClips.end()) {
2462             // One of the clip was removed, delete the mix
2463             qDebug() << "=== CLIPS: " << firstClip << " / " << secondClipId << " ARE MISSING!!!!";
2464             Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[secondClipId]->getAsset());
2465             QScopedPointer<Mlt::Field> field(m_track->field());
2466             field->lock();
2467             field->disconnect_service(transition);
2468             field->unlock();
2469             toDelete << secondClipId;
2470             m_mixList.remove(firstClip);
2471             continue;
2472         }
2473         // Asjust mix in/out
2474         int mixIn = m_allClips[secondClipId]->getPosition();
2475         int mixOut = m_allClips[firstClip]->getPosition() + m_allClips[firstClip]->getPlaytime();
2476         if (mixOut <= mixIn) {
2477             if (finalMove) {
2478                 // Delete mix
2479                 mixOut = mixIn;
2480             } else {
2481                 mixOut = mixIn + 1;
2482             }
2483         }
2484         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[secondClipId]->getAsset());
2485         if (mixIn == mixOut) {
2486             QScopedPointer<Mlt::Field> field(m_track->field());
2487             field->lock();
2488             field->disconnect_service(transition);
2489             field->unlock();
2490             toDelete << secondClipId;
2491             m_mixList.remove(firstClip);
2492         } else {
2493             transition.set_in_and_out(mixIn, mixOut);
2494         }
2495         if (auto ptr = m_parent.lock()) {
2496             ptr->getClipPtr(secondClipId)->setMixDuration(mixOut - mixIn);
2497             QModelIndex ix = ptr->makeClipIndexFromID(secondClipId);
2498             Q_EMIT ptr->dataChanged(ix, ix, {TimelineModel::MixRole, TimelineModel::MixCutRole});
2499         }
2500     }
2501     for (int i : qAsConst(toDelete)) {
2502         m_sameCompositions.erase(i);
2503     }
2504 }
2505 
2506 int TrackModel::mixCount() const
2507 {
2508     Q_ASSERT(m_mixList.size() == int(m_sameCompositions.size()));
2509     return m_mixList.size();
2510 }
2511 
2512 bool TrackModel::hasMix(int cid) const
2513 {
2514     if (m_mixList.contains(cid)) {
2515         return true;
2516     }
2517     if (m_sameCompositions.count(cid) > 0) {
2518         return true;
2519     }
2520     return false;
2521 }
2522 
2523 bool TrackModel::hasStartMix(int cid) const
2524 {
2525     return m_sameCompositions.count(cid) > 0;
2526 }
2527 
2528 int TrackModel::isOnCut(int cid)
2529 {
2530     if (auto ptr = m_parent.lock()) {
2531         std::shared_ptr<CompositionModel> composition = ptr->getCompositionPtr(cid);
2532         // Start and end pos are incremented by 1 to account snapping
2533         int startPos = composition->getPosition() - 1;
2534         int endPos = startPos + composition->getPlaytime() + 1;
2535         int cid1 = getClipByPosition(startPos);
2536         int cid2 = getClipByPosition(endPos);
2537         if (cid1 == -1 || cid2 == -1 || cid1 == cid2) {
2538             return -1;
2539         }
2540         if (m_mixList.contains(cid1) || m_sameCompositions.count(cid2) > 0) {
2541             // Clips are already mixed, abort
2542             return -1;
2543         }
2544         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(cid2);
2545         int mixRight = clip->getPosition();
2546         std::shared_ptr<ClipModel> clip2 = ptr->getClipPtr(cid1);
2547         int mixLeft = clip2->getPosition() + clip2->getPlaytime();
2548         if (mixLeft == mixRight) {
2549             return mixLeft;
2550         }
2551     }
2552     return -1;
2553 }
2554 
2555 bool TrackModel::hasEndMix(int cid) const
2556 {
2557     return m_mixList.contains(cid);
2558 }
2559 
2560 int TrackModel::getSecondMixPartner(int cid) const
2561 {
2562     return hasEndMix(cid) ? m_mixList.find(cid).value() : -1;
2563 }
2564 
2565 QDomElement TrackModel::mixXml(QDomDocument &document, int cid) const
2566 {
2567     QDomElement container = document.createElement(QStringLiteral("mix"));
2568     int firstClipId = m_mixList.key(cid);
2569     container.setAttribute(QStringLiteral("firstClip"), firstClipId);
2570     container.setAttribute(QStringLiteral("secondClip"), cid);
2571     if (auto ptr = m_parent.lock()) {
2572         std::shared_ptr<ClipModel> clip = ptr->getClipPtr(firstClipId);
2573         container.setAttribute(QStringLiteral("firstClipPosition"), clip->getPosition());
2574     }
2575     const QString assetId = m_sameCompositions.at(cid)->getAssetId();
2576     QVector<QPair<QString, QVariant>> params = m_sameCompositions.at(cid)->getAllParameters();
2577     container.setAttribute(QStringLiteral("asset"), assetId);
2578     for (const auto &p : qAsConst(params)) {
2579         QDomElement para = document.createElement(QStringLiteral("param"));
2580         para.setAttribute(QStringLiteral("name"), p.first);
2581         QDomText val = document.createTextNode(p.second.toString());
2582         para.appendChild(val);
2583         container.appendChild(para);
2584     }
2585     std::pair<int, int> tracks = getMixTracks(cid);
2586     container.setAttribute(QStringLiteral("a_track"), tracks.first);
2587     container.setAttribute(QStringLiteral("b_track"), tracks.second);
2588 
2589     std::pair<MixInfo, MixInfo> mixData = getMixInfo(cid);
2590     container.setAttribute(QStringLiteral("mixStart"), mixData.first.secondClipInOut.first);
2591     container.setAttribute(QStringLiteral("mixEnd"), mixData.first.firstClipInOut.second);
2592     container.setAttribute(QStringLiteral("mixOffset"), mixData.first.mixOffset);
2593     return container;
2594 }
2595 
2596 bool TrackModel::loadMix(Mlt::Transition *t)
2597 {
2598     int in = t->get_in();
2599     int out = t->get_out() - 1;
2600     bool reverse = t->get_int("reverse") == 1;
2601     int cid1 = getClipByPosition(in, reverse ? 1 : 0);
2602     int cid2 = getClipByPosition(out, reverse ? 0 : 1);
2603     if (cid1 < 0 || cid2 < 0) {
2604         qDebug() << "INVALID CLIP MIX: " << cid1 << " - " << cid2;
2605         // Check if reverse setting was not correctly set
2606         cid1 = getClipByPosition(in, reverse ? 0 : 1);
2607         cid2 = getClipByPosition(out, reverse ? 1 : 0);
2608         if (cid1 < 0 || cid2 < 0) {
2609             QScopedPointer<Mlt::Field> field(m_track->field());
2610             field->lock();
2611             field->disconnect_service(*t);
2612             field->unlock();
2613             return false;
2614         }
2615     } else {
2616         int firstClipIn = m_allClips[cid1]->getPosition();
2617         if (in == firstClipIn && in != m_allClips[cid2]->getPosition()) {
2618             qDebug() << "/// SWAPPING CLIPS";
2619             if (m_allClips[cid1]->getPosition() > m_allClips[cid2]->getPosition()) {
2620                 // Incorrecty detected revert mix
2621                 std::swap(cid1, cid2);
2622             }
2623         }
2624         if (m_allClips[cid1]->getPosition() > m_allClips[cid2]->getPosition() ||
2625             (m_allClips[cid1]->getPosition() + m_allClips[cid1]->getPlaytime()) > (m_allClips[cid2]->getPosition() + m_allClips[cid2]->getPlaytime())) {
2626             // Impossible mix, remove
2627             QScopedPointer<Mlt::Field> field(m_track->field());
2628             field->lock();
2629             field->disconnect_service(*t);
2630             field->unlock();
2631             return false;
2632         }
2633     }
2634     // Ensure in/out points are in sync with the clips
2635     int clipIn = m_allClips[cid2]->getPosition();
2636     int clipOut = m_allClips[cid1]->getPosition() + m_allClips[cid1]->getPlaytime();
2637     if (in != clipIn || out != clipOut) {
2638         t->set_in_and_out(clipIn, clipOut);
2639     }
2640     QString assetId(t->get("kdenlive_id"));
2641     if (assetId.isEmpty()) {
2642         assetId = QString(t->get("mlt_service"));
2643     }
2644     std::unique_ptr<Mlt::Transition> tr(t);
2645     QDomElement xml = TransitionsRepository::get()->getXml(assetId);
2646     // Paste parameters from existing mix
2647     // std::unique_ptr<Mlt::Properties> sourceProperties(t);
2648     QStringList sourceProps;
2649     for (int i = 0; i < tr->count(); i++) {
2650         sourceProps << tr->get_name(i);
2651     }
2652     QDomNodeList params = xml.elementsByTagName(QStringLiteral("parameter"));
2653     for (int i = 0; i < params.count(); ++i) {
2654         QDomElement currentParameter = params.item(i).toElement();
2655         QString paramName = currentParameter.attribute(QStringLiteral("name"));
2656         if (!sourceProps.contains(paramName)) {
2657             continue;
2658         }
2659         QString paramValue = tr->get(paramName.toUtf8().constData());
2660         currentParameter.setAttribute(QStringLiteral("value"), paramValue);
2661     }
2662     QUuid timelineUuid;
2663     if (auto ptr = m_parent.lock()) {
2664         timelineUuid = ptr->uuid();
2665     }
2666     std::shared_ptr<AssetParameterModel> asset(
2667         new AssetParameterModel(std::move(tr), xml, assetId, ObjectId(KdenliveObjectType::TimelineMix, cid2, timelineUuid), QString()));
2668     m_sameCompositions[cid2] = asset;
2669     m_mixList.insert(cid1, cid2);
2670     int mixDuration = t->get_length() - 1;
2671     int mixCutPos = qMin(t->get_int("kdenlive:mixcut"), mixDuration);
2672     setMixDuration(cid2, mixDuration, mixCutPos);
2673     return true;
2674 }
2675 
2676 const std::shared_ptr<AssetParameterModel> TrackModel::mixModel(int cid)
2677 {
2678     if (m_sameCompositions.count(cid) > 0) {
2679         return m_sameCompositions[cid];
2680     }
2681     return nullptr;
2682 }
2683 
2684 bool TrackModel::reAssignEndMix(int currentId, int newId)
2685 {
2686     Q_ASSERT(m_mixList.contains(currentId));
2687     int mixedClip = m_mixList.value(currentId);
2688     m_mixList.remove(currentId);
2689     m_mixList.insert(newId, mixedClip);
2690     return true;
2691 }
2692 
2693 std::pair<int, int> TrackModel::getMixTracks(int cid) const
2694 {
2695     Q_ASSERT(m_sameCompositions.count(cid) > 0);
2696     int a_track = m_sameCompositions.at(cid)->getAsset()->get_int("a_track");
2697     int b_track = m_sameCompositions.at(cid)->getAsset()->get_int("b_track");
2698     return {a_track, b_track};
2699 }
2700 
2701 std::pair<QString, QVector<QPair<QString, QVariant>>> TrackModel::getMixParams(int cid)
2702 {
2703     Q_ASSERT(m_sameCompositions.count(cid) > 0);
2704     const QString assetId = m_sameCompositions[cid]->getAssetId();
2705     QVector<QPair<QString, QVariant>> params = m_sameCompositions[cid]->getAllParameters();
2706     return {assetId, params};
2707 }
2708 
2709 void TrackModel::switchMix(int cid, const QString &composition, Fun &undo, Fun &redo)
2710 {
2711     // First remove existing mix
2712     // lock MLT playlist so that we don't end up with invalid frames in monitor
2713     const QString currentAsset = m_sameCompositions[cid]->getAssetId();
2714     QVector<QPair<QString, QVariant>> allParams = m_sameCompositions[cid]->getAllParameters();
2715     std::pair<int, int> originTracks = getMixTracks(cid);
2716     // Check if mix should be reversed
2717     bool reverse = m_allClips[cid]->getSubPlaylistIndex() == 0;
2718     Fun local_redo = [this, cid, composition, reverse]() {
2719         m_playlists[0].lock();
2720         m_playlists[1].lock();
2721         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[cid]->getAsset());
2722         int in = transition.get_in();
2723         int out = transition.get_out();
2724         int mixCutPos = transition.get_int("kdenlive:mixcut");
2725         QScopedPointer<Mlt::Field> field(m_track->field());
2726         field->lock();
2727         field->disconnect_service(transition);
2728         field->unlock();
2729         m_sameCompositions.erase(cid);
2730         if (auto ptr = m_parent.lock()) {
2731             std::unique_ptr<Mlt::Transition> t = TransitionsRepository::get()->getTransition(composition);
2732             t->set_in_and_out(in, out);
2733             if (reverse) {
2734                 t->set_tracks(1, 0);
2735                 m_track->plant_transition(*t.get(), 1, 0);
2736             } else {
2737                 t->set_tracks(0, 1);
2738                 m_track->plant_transition(*t.get(), 0, 1);
2739             }
2740             t->set("kdenlive:mixcut", mixCutPos);
2741             QDomElement xml = TransitionsRepository::get()->getXml(composition);
2742             std::shared_ptr<AssetParameterModel> asset(
2743                 new AssetParameterModel(std::move(t), xml, composition, ObjectId(KdenliveObjectType::TimelineMix, cid, ptr->uuid()), QString()));
2744             m_sameCompositions[cid] = asset;
2745         }
2746         m_playlists[0].unlock();
2747         m_playlists[1].unlock();
2748         return true;
2749     };
2750     Fun local_undo = [this, cid, currentAsset, allParams, originTracks]() {
2751         m_playlists[0].lock();
2752         m_playlists[1].lock();
2753         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions[cid]->getAsset());
2754         int in = transition.get_in();
2755         int out = transition.get_out();
2756         QScopedPointer<Mlt::Field> field(m_track->field());
2757         field->lock();
2758         field->disconnect_service(transition);
2759         field->unlock();
2760         m_sameCompositions.erase(cid);
2761         if (auto ptr = m_parent.lock()) {
2762             std::unique_ptr<Mlt::Transition> t = TransitionsRepository::get()->getTransition(currentAsset);
2763             t->set_in_and_out(in, out);
2764             t->set_tracks(originTracks.first, originTracks.second);
2765             m_track->plant_transition(*t.get(), originTracks.first, originTracks.second);
2766             QDomElement xml = TransitionsRepository::get()->getXml(currentAsset);
2767             QDomNodeList xmlParams = xml.elementsByTagName(QStringLiteral("parameter"));
2768             for (int i = 0; i < xmlParams.count(); ++i) {
2769                 QDomElement currentParameter = xmlParams.item(i).toElement();
2770                 QString paramName = currentParameter.attribute(QStringLiteral("name"));
2771                 for (const auto &p : qAsConst(allParams)) {
2772                     if (p.first == paramName) {
2773                         currentParameter.setAttribute(QStringLiteral("value"), p.second.toString());
2774                         break;
2775                     }
2776                 }
2777             }
2778             std::shared_ptr<AssetParameterModel> asset(
2779                 new AssetParameterModel(std::move(t), xml, currentAsset, ObjectId(KdenliveObjectType::TimelineMix, cid, ptr->uuid()), QString()));
2780             m_sameCompositions[cid] = asset;
2781         }
2782         m_playlists[0].unlock();
2783         m_playlists[1].unlock();
2784         return true;
2785     };
2786     UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
2787 }
2788 
2789 void TrackModel::reverseCompositionXml(const QString &composition, QDomElement xml)
2790 {
2791     if (composition == QLatin1String("luma") || composition == QLatin1String("dissolve") || composition == QLatin1String("mix")) {
2792         Xml::setXmlParameter(xml, QStringLiteral("reverse"), QStringLiteral("1"));
2793     } else if (composition == QLatin1String("composite")) {
2794         Xml::setXmlParameter(xml, QStringLiteral("invert"), QStringLiteral("1"));
2795     } else if (composition == QLatin1String("wipe")) {
2796         Xml::setXmlParameter(xml, QStringLiteral("geometry"), QStringLiteral("0=0% 0% 100% 100% 100%;-1=0% 0% 100% 100% 0%"));
2797     } else if (composition == QLatin1String("slide")) {
2798         Xml::setXmlParameter(xml, QStringLiteral("rect"), QStringLiteral("0=0% 0% 100% 100% 100%;-1=100% 0% 100% 100% 100%"));
2799     }
2800 }
2801 
2802 void TrackModel::updateCompositionDirection(Mlt::Transition &transition, bool reverse)
2803 {
2804     if (reverse) {
2805         transition.set_tracks(1, 0);
2806         m_track->plant_transition(transition, 1, 0);
2807     } else {
2808         transition.set_tracks(0, 1);
2809         m_track->plant_transition(transition, 0, 1);
2810     }
2811 }
2812 
2813 bool TrackModel::mixIsReversed(int cid) const
2814 {
2815     if (m_sameCompositions.count(cid) > 0) {
2816         // There is a mix at clip start, adjust direction
2817         Mlt::Transition &transition = *static_cast<Mlt::Transition *>(m_sameCompositions.at(cid)->getAsset());
2818         if (isAudioTrack()) {
2819             qDebug() << "::: CHKING TRANSITION ON AUDIO TRACK: " << transition.get_int("reverse");
2820             return transition.get_int("reverse") == 1;
2821         }
2822         qDebug() << "::: CHKING TRANSITION ON video TRACK: ";
2823         return (transition.get_a_track() == 1 && transition.get_b_track() == 0);
2824     }
2825     return false;
2826 }
2827 
2828 QVariantList TrackModel::stackZones() const
2829 {
2830     return m_effectStack->getEffectZones();
2831 }
2832 
2833 bool TrackModel::hasClipStart(int pos)
2834 {
2835     for (auto &m_playlist : m_playlists) {
2836         if (m_playlist.is_blank_at(pos)) {
2837             continue;
2838         }
2839         if (pos == 0 || m_playlist.get_clip_index_at(pos) != m_playlist.get_clip_index_at(pos - 1)) {
2840             return true;
2841         }
2842     }
2843     return false;
2844 }
2845 
2846 QByteArray TrackModel::trackHash()
2847 {
2848     QByteArray fileData;
2849     // Parse clips
2850     for (auto &clip : m_allClips) {
2851         fileData.append(clip.second->clipHash().toUtf8());
2852     }
2853     // Parse mixes
2854     for (auto &sameComposition : m_sameCompositions) {
2855         Mlt::Transition *tr = static_cast<Mlt::Transition *>(sameComposition.second->getAsset());
2856         QString mixData = QString("%1 %2 %3").arg(QString::number(tr->get_in()), QString::number(tr->get_out()), sameComposition.second->getAssetId());
2857         fileData.append(mixData.toLatin1());
2858     }
2859     return fileData;
2860 }