1 /*
2     SPDX-FileCopyrightText: 2017 Nicolas Carion
3     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
4 */
5 
6 #include "timelineitemmodel.hpp"
7 #include "audiomixer/mixermanager.hpp"
8 #include "assets/keyframes/model/keyframemodel.hpp"
9 #include "bin/model/markerlistmodel.hpp"
10 #include "bin/model/subtitlemodel.hpp"
11 #include "clipmodel.hpp"
12 #include "compositionmodel.hpp"
13 #include "core.h"
14 #include "doc/docundostack.hpp"
15 #include "groupsmodel.hpp"
16 #include "kdenlivesettings.h"
17 #include "macros.hpp"
18 #include "trackmodel.hpp"
19 #include "snapmodel.hpp"
20 #include "transitions/transitionsrepository.hpp"
21 #include <QDebug>
22 #include <QFileInfo>
23 #include <mlt++/MltField.h>
24 #include <mlt++/MltProfile.h>
25 #include <mlt++/MltTractor.h>
26 #include <mlt++/MltTransition.h>
27 
28 #ifdef CRASH_AUTO_TEST
29 #pragma GCC diagnostic push
30 #pragma GCC diagnostic ignored "-Wunused-parameter"
31 #pragma GCC diagnostic ignored "-Wsign-conversion"
32 #pragma GCC diagnostic ignored "-Wfloat-equal"
33 #pragma GCC diagnostic ignored "-Wshadow"
34 #pragma GCC diagnostic ignored "-Wpedantic"
35 #include <rttr/registration>
36 #pragma GCC diagnostic pop
37 RTTR_REGISTRATION
38 {
39     using namespace rttr;
40     registration::class_<TimelineItemModel>("TimelineItemModel");
41 }
42 #endif
43 
44 TimelineItemModel::TimelineItemModel(Mlt::Profile *profile, std::weak_ptr<DocUndoStack> undo_stack)
45     : TimelineModel(profile, std::move(undo_stack))
46 {
47 }
48 
finishConstruct(const std::shared_ptr<TimelineItemModel> & ptr,const std::shared_ptr<MarkerListModel> & guideModel)49 void TimelineItemModel::finishConstruct(const std::shared_ptr<TimelineItemModel> &ptr, const std::shared_ptr<MarkerListModel> &guideModel)
50 {
51     ptr->weak_this_ = ptr;
52     ptr->m_groups = std::make_unique<GroupsModel>(ptr);
53     guideModel->registerSnapModel(std::static_pointer_cast<SnapInterface>(ptr->m_snaps));
54 }
55 
construct(Mlt::Profile * profile,std::shared_ptr<MarkerListModel> guideModel,std::weak_ptr<DocUndoStack> undo_stack)56 std::shared_ptr<TimelineItemModel> TimelineItemModel::construct(Mlt::Profile *profile, std::shared_ptr<MarkerListModel> guideModel,
57                                                                 std::weak_ptr<DocUndoStack> undo_stack)
58 {
59     std::shared_ptr<TimelineItemModel> ptr(new TimelineItemModel(profile, std::move(undo_stack)));
60     finishConstruct(ptr, std::move(guideModel));
61     return ptr;
62 }
63 
64 TimelineItemModel::~TimelineItemModel() = default;
65 
index(int row,int column,const QModelIndex & parent) const66 QModelIndex TimelineItemModel::index(int row, int column, const QModelIndex &parent) const
67 {
68     READ_LOCK();
69     QModelIndex result;
70     if (parent.isValid()) {
71         auto trackId = int(parent.internalId());
72         Q_ASSERT(isTrack(trackId));
73         int clipId = getTrackById_const(trackId)->getClipByRow(row);
74         if (clipId != -1) {
75             result = createIndex(row, 0, quintptr(clipId));
76         } else if (row < getTrackClipsCount(trackId) + getTrackCompositionsCount(trackId)) {
77             int compoId = getTrackById_const(trackId)->getCompositionByRow(row);
78             if (compoId != -1) {
79                 result = createIndex(row, 0, quintptr(compoId));
80             }
81         } else {
82             // Invalid index requested
83             Q_ASSERT(false);
84         }
85     } else if (row < int(m_allTracks.size()) && row >= 0) {
86         // Get sort order
87         // row = getTracksCount() - 1 - row;
88         auto it = m_allTracks.cbegin();
89         std::advance(it, row);
90         int trackId = (*it)->getId();
91         result = createIndex(row, column, quintptr(trackId));
92     }
93     return result;
94 }
95 
96 /*QModelIndex TimelineItemModel::makeIndex(int trackIndex, int clipIndex) const
97 {
98     return index(clipIndex, 0, index(trackIndex));
99 }*/
100 
makeClipIndexFromID(int clipId) const101 QModelIndex TimelineItemModel::makeClipIndexFromID(int clipId) const
102 {
103     Q_ASSERT(m_allClips.count(clipId) > 0);
104     int trackId = m_allClips.at(clipId)->getCurrentTrackId();
105     if (trackId == -1) {
106         // Clip is not inserted in a track
107         qDebug() << "/// WARNING; INVALID CLIP INDEX REQUESTED: "<<clipId<<"\n________________";
108         return {};
109     }
110     int row = getTrackById_const(trackId)->getRowfromClip(clipId);
111     return index(row, 0, makeTrackIndexFromID(trackId));
112 }
113 
makeCompositionIndexFromID(int compoId) const114 QModelIndex TimelineItemModel::makeCompositionIndexFromID(int compoId) const
115 {
116     Q_ASSERT(m_allCompositions.count(compoId) > 0);
117     int trackId = m_allCompositions.at(compoId)->getCurrentTrackId();
118     return index(getTrackById_const(trackId)->getRowfromComposition(compoId), 0, makeTrackIndexFromID(trackId));
119 }
120 
subtitleChanged(int subId,const QVector<int> roles)121 void TimelineItemModel::subtitleChanged(int subId, const QVector<int> roles)
122 {
123     if (m_closing) {
124         return;
125     }
126     Q_ASSERT(m_subtitleModel != nullptr);
127     Q_ASSERT(m_allSubtitles.count(subId) > 0);
128     m_subtitleModel->updateSub(subId, roles);
129 }
130 
makeTrackIndexFromID(int trackId) const131 QModelIndex TimelineItemModel::makeTrackIndexFromID(int trackId) const
132 {
133     // we retrieve iterator
134     Q_ASSERT(m_iteratorTable.count(trackId) > 0);
135     auto it = m_iteratorTable.at(trackId);
136     int ind = int(std::distance<decltype(m_allTracks.cbegin())>(m_allTracks.begin(), it));
137     // Get sort order
138     // ind = getTracksCount() - 1 - ind;
139     return index(ind);
140 }
141 
parent(const QModelIndex & index) const142 QModelIndex TimelineItemModel::parent(const QModelIndex &index) const
143 {
144     READ_LOCK();
145     // qDebug() << "TimelineItemModel::parent"<< index;
146     if (index == QModelIndex()) {
147         return index;
148     }
149     const int id = static_cast<int>(index.internalId());
150     if (!index.isValid() || isTrack(id)) {
151         return QModelIndex();
152     }
153     if (isClip(id)) {
154         const int trackId = getClipTrackId(id);
155         return makeTrackIndexFromID(trackId);
156     }
157     if (isComposition(id)) {
158         const int trackId = getCompositionTrackId(id);
159         return makeTrackIndexFromID(trackId);
160     }
161     return {};
162 }
163 
rowCount(const QModelIndex & parent) const164 int TimelineItemModel::rowCount(const QModelIndex &parent) const
165 {
166     READ_LOCK();
167     if (parent.isValid()) {
168         const int id = int(parent.internalId());
169         if (!isTrack(id)) {
170             // clips don't have children
171             // if it is not a track, it is something invalid
172             return 0;
173         }
174         return getTrackClipsCount(id) + getTrackCompositionsCount(id);
175     }
176     return int(m_allTracks.size());
177 }
178 
columnCount(const QModelIndex & parent) const179 int TimelineItemModel::columnCount(const QModelIndex &parent) const
180 {
181     Q_UNUSED(parent);
182     return 1;
183 }
184 
roleNames() const185 QHash<int, QByteArray> TimelineItemModel::roleNames() const
186 {
187     QHash<int, QByteArray> roles;
188     roles[NameRole] = "name";
189     roles[ResourceRole] = "resource";
190     roles[ServiceRole] = "mlt_service";
191     roles[BinIdRole] = "binId";
192     roles[TrackIdRole] = "trackId";
193     roles[TagRole] = "tag";
194     roles[FakeTrackIdRole] = "fakeTrackId";
195     roles[FakePositionRole] = "fakePosition";
196     roles[StartRole] = "start";
197     roles[MixRole] = "mixDuration";
198     roles[MixCutRole] = "mixCut";
199     roles[DurationRole] = "duration";
200     roles[MaxDurationRole] = "maxDuration";
201     roles[MarkersRole] = "markers";
202     roles[KeyframesRole] = "keyframeModel";
203     roles[ShowKeyframesRole] = "showKeyframes";
204     roles[PlaylistStateRole] = "clipState";
205     roles[StatusRole] = "clipStatus";
206     roles[TypeRole] = "clipType";
207     roles[InPointRole] = "in";
208     roles[OutPointRole] = "out";
209     roles[FramerateRole] = "fps";
210     roles[GroupedRole] = "grouped";
211     roles[IsDisabledRole] = "disabled";
212     roles[IsAudioRole] = "audio";
213     roles[AudioChannelsRole] = "audioChannels";
214     roles[AudioStreamRole] = "audioStream";
215     roles[AudioMultiStreamRole] = "multiStream";
216     roles[AudioStreamIndexRole] = "audioStreamIndex";
217     roles[IsCompositeRole] = "composite";
218     roles[IsLockedRole] = "locked";
219     roles[FadeInRole] = "fadeIn";
220     roles[FadeOutRole] = "fadeOut";
221     roles[FileHashRole] = "hash";
222     roles[SpeedRole] = "speed";
223     roles[TimeRemapRole] = "timeremap";
224     roles[HeightRole] = "trackHeight";
225     roles[TrackTagRole] = "trackTag";
226     roles[ItemIdRole] = "item";
227     roles[ItemATrack] = "a_track";
228     roles[HasAudio] = "hasAudio";
229     roles[CanBeAudioRole] = "canBeAudio";
230     roles[CanBeVideoRole] = "canBeVideo";
231     roles[ReloadThumbRole] = "reloadThumb";
232     roles[PositionOffsetRole] = "positionOffset";
233     roles[ThumbsFormatRole] = "thumbsFormat";
234     roles[AudioRecordRole] = "audioRecord";
235     roles[TrackActiveRole] = "trackActive";
236     roles[EffectNamesRole] = "effectNames";
237     roles[EffectsEnabledRole] = "isStackEnabled";
238     roles[EffectZonesRole] = "effectZones";
239     roles[GrabbedRole] = "isGrabbed";
240     roles[SelectedRole] = "selected";
241     return roles;
242 }
243 
data(const QModelIndex & index,int role) const244 QVariant TimelineItemModel::data(const QModelIndex &index, int role) const
245 {
246     READ_LOCK();
247     if (!m_tractor || !index.isValid()) {
248         // qDebug() << "DATA abort. Index validity="<<index.isValid();
249         return QVariant();
250     }
251     const int id = int(index.internalId());
252     if (role == ItemIdRole) {
253         return id;
254     }
255     if (role == SortRole) {
256         if (isTrack(id)) {
257             return getTrackSortValue(id, KdenliveSettings::audiotracksbelow());
258         }
259         return QVariant();
260     }
261     if (isClip(id)) {
262         // qDebug() << "REQUESTING DATA "<<roleNames()[role]<<index;
263         std::shared_ptr<ClipModel> clip = m_allClips.at(id);
264         // Get data for a clip
265         switch (role) {
266         // TODO
267         case NameRole:
268         case Qt::DisplayRole: {
269             return clip->clipName();
270         }
271         case ResourceRole: {
272             QString result = clip->getProperty("resource");
273             if (result == QLatin1String("<producer>")) {
274                 result = clip->getProperty("mlt_service");
275             }
276             return result;
277         }
278         case StatusRole: {
279             return clip->clipStatus();
280         }
281         case FakeTrackIdRole:
282             return clip->getFakeTrackId();
283         case FakePositionRole:
284             return clip->getFakePosition();
285         case BinIdRole:
286             return clip->binId();
287         case TrackIdRole:
288             return clip->getCurrentTrackId();
289         case ServiceRole:
290             return clip->getProperty("mlt_service");
291             break;
292         case AudioChannelsRole:
293             return clip->audioChannels();
294         case AudioStreamRole:
295             return clip->audioStream();
296         case AudioMultiStreamRole:
297             return clip->audioMultiStream();
298         case AudioStreamIndexRole:
299             return clip->audioStreamIndex();
300         case HasAudio:
301             return clip->audioEnabled();
302         case IsAudioRole:
303             return clip->isAudioOnly();
304         case CanBeAudioRole:
305             return clip->canBeAudio();
306         case CanBeVideoRole:
307             return clip->canBeVideo();
308         case MarkersRole: {
309             return QVariant::fromValue<MarkerListModel *>(clip->getMarkerModel().get());
310         }
311         case KeyframesRole: {
312             return QVariant::fromValue<KeyframeModel *>(clip->getKeyframeModel());
313         }
314         case PlaylistStateRole:
315             return QVariant::fromValue(clip->clipState());
316         case TypeRole:
317             return QVariant::fromValue(clip->clipType());
318         case StartRole:
319             return clip->getPosition();
320         case DurationRole:
321             return clip->getPlaytime();
322         case MaxDurationRole:
323             return clip->getMaxDuration();
324         case GroupedRole:
325             return m_groups->isInGroup(id);
326         case EffectNamesRole:
327             return clip->effectNames();
328         case InPointRole:
329             return clip->getIn();
330         case OutPointRole:
331             return clip->getOut();
332         case ShowKeyframesRole:
333             return clip->showKeyframes();
334         case FadeInRole:
335             return clip->fadeIn();
336         case FadeOutRole:
337             return clip->fadeOut();
338         case MixRole:
339             return clip->getMixDuration();
340         case MixCutRole:
341             return clip->getMixCutPosition();
342         case ReloadThumbRole:
343             return clip->forceThumbReload;
344         case PositionOffsetRole:
345             return clip->getOffset();
346         case SpeedRole:
347             return clip->getSpeed();
348         case GrabbedRole:
349             return clip->isGrabbed();
350         case SelectedRole:
351             return clip->selected;
352         case TagRole:
353             return clip->clipTag();
354         case TimeRemapRole:
355             return clip->isChain();
356         default:
357             break;
358         }
359     } else if (isTrack(id)) {
360         // qDebug() << "DATA REQUESTED FOR TRACK "<< id;
361         switch (role) {
362         case NameRole:
363         case Qt::DisplayRole: {
364             return getTrackById_const(id)->getProperty("kdenlive:track_name").toString();
365         }
366         case TypeRole:
367             return QVariant::fromValue(ClipType::ProducerType::Track);
368         case DurationRole:
369             // qDebug() << "DATA yielding duration" << m_tractor->get_playtime();
370             return getTrackById_const(id)->trackDuration();
371         case IsDisabledRole:
372             // qDebug() << "DATA yielding mute" << 0;
373             return getTrackById_const(id)->isAudioTrack() ? getTrackById_const(id)->isMute() : getTrackById_const(id)->isHidden();
374         case IsAudioRole:
375             return getTrackById_const(id)->isAudioTrack();
376         case TrackTagRole:
377             return getTrackTagById(id);
378         case IsLockedRole:
379             return getTrackById_const(id)->isLocked();
380         case HeightRole: {
381             int collapsed = getTrackById_const(id)->getProperty("kdenlive:collapsed").toInt();
382             if (collapsed > 0) {
383                 return collapsed;
384             }
385             int height = getTrackById_const(id)->getProperty("kdenlive:trackheight").toInt();
386             // qDebug() << "DATA yielding height" << height;
387             return (height > 0 ? height : KdenliveSettings::trackheight());
388         }
389         case ThumbsFormatRole:
390             return getTrackById_const(id)->getProperty("kdenlive:thumbs_format").toInt();
391         case IsCompositeRole: {
392         case AudioRecordRole:
393             return getTrackById_const(id)->getProperty("kdenlive:audio_rec").toInt();
394         }
395         case TrackActiveRole: {
396             return getTrackById_const(id)->isTimelineActive();
397         }
398         case EffectNamesRole: {
399             return getTrackById_const(id)->effectNames();
400         }
401         case EffectsEnabledRole: {
402             return getTrackById_const(id)->stackEnabled();
403         }
404         case EffectZonesRole: {
405             return getTrackById_const(id)->stackZones();
406         }
407         default:
408             break;
409         }
410     } else if (isComposition(id)) {
411         std::shared_ptr<CompositionModel> compo = m_allCompositions.at(id);
412         switch (role) {
413         case NameRole:
414         case Qt::DisplayRole:
415         case ResourceRole:
416         case ServiceRole:
417             return compo->displayName();
418             break;
419         case TypeRole:
420             return QVariant::fromValue(ClipType::ProducerType::Composition);
421         case StartRole:
422             return compo->getPosition();
423         case TrackIdRole:
424             return compo->getCurrentTrackId();
425         case DurationRole:
426             return compo->getPlaytime();
427         case GroupedRole:
428             return m_groups->isInGroup(id);
429         case InPointRole:
430             return 0;
431         case OutPointRole:
432             return 100;
433         case BinIdRole:
434             return 5;
435         case KeyframesRole: {
436             return QVariant::fromValue<KeyframeModel *>(compo->getEffectKeyframeModel());
437         }
438         case ShowKeyframesRole:
439             return compo->showKeyframes();
440         case ItemATrack:
441             return compo->getForcedTrack();
442         case MarkersRole: {
443             QVariantList markersList;
444             return markersList;
445         }
446         case GrabbedRole:
447             return compo->isGrabbed();
448         case SelectedRole:
449             return compo->selected;
450         default:
451             break;
452         }
453     } else {
454         qDebug() << "UNKNOWN DATA requested " << index << roleNames()[role];
455     }
456     return QVariant();
457 }
458 
setTrackName(int trackId,const QString & text)459 void TimelineItemModel::setTrackName(int trackId, const QString &text)
460 {
461     QWriteLocker locker(&m_lock);
462     const QString &currentName = getTrackProperty(trackId, QStringLiteral("kdenlive:track_name")).toString();
463     if (text == currentName) {
464         return;
465     }
466     Fun undo_lambda = [this, trackId, currentName]() {
467         setTrackProperty(trackId, QStringLiteral("kdenlive:track_name"), currentName);
468         return true;
469     };
470     Fun redo_lambda = [this, trackId, text]() {
471         setTrackProperty(trackId, QStringLiteral("kdenlive:track_name"), text);
472         return true;
473     };
474     redo_lambda();
475     PUSH_UNDO(undo_lambda, redo_lambda, i18n("Rename Track"));
476 }
477 
hideTrack(int trackId,const QString state)478 void TimelineItemModel::hideTrack(int trackId, const QString state)
479 {
480     QWriteLocker locker(&m_lock);
481     QString previousState = getTrackProperty(trackId, QStringLiteral("hide")).toString();
482     Fun undo_lambda = [this, trackId, previousState]() {
483         setTrackProperty(trackId, QStringLiteral("hide"), previousState);
484         return true;
485     };
486     Fun redo_lambda = [this, trackId, state]() {
487         setTrackProperty(trackId, QStringLiteral("hide"), state);
488         return true;
489     };
490     redo_lambda();
491     PUSH_UNDO(undo_lambda, redo_lambda, state == QLatin1String("3") ? i18n("Hide Track") : i18n("Enable Track"));
492 }
493 
setTrackProperty(int trackId,const QString & name,const QString & value)494 void TimelineItemModel::setTrackProperty(int trackId, const QString &name, const QString &value)
495 {
496     std::shared_ptr<TrackModel> track = getTrackById(trackId);
497     track->setProperty(name, value);
498     QVector<int> roles;
499     bool updateMultiTrack = false;
500     if (name == QLatin1String("kdenlive:track_name")) {
501         roles.push_back(NameRole);
502         if (!track->isAudioTrack()) {
503             updateMultiTrack = true;
504         }
505     } else if (name == QLatin1String("kdenlive:locked_track")) {
506         roles.push_back(IsLockedRole);
507     } else if (name == QLatin1String("hide")) {
508         roles.push_back(IsDisabledRole);
509         if (!track->isAudioTrack()) {
510             pCore->invalidateItem(ObjectId(ObjectType::TimelineTrack, trackId));
511             pCore->requestMonitorRefresh();
512             updateMultiTrack = true;
513         }
514     } else if (name == QLatin1String("kdenlive:timeline_active")) {
515         roles.push_back(TrackActiveRole);
516     } else if (name == QLatin1String("kdenlive:thumbs_format")) {
517         roles.push_back(ThumbsFormatRole);
518     } else if (name == QLatin1String("kdenlive:collapsed")) {
519         roles.push_back(HeightRole);
520     } else if (name == QLatin1String("kdenlive:audio_rec")) {
521         roles.push_back(AudioRecordRole);
522     }
523     if (!roles.isEmpty()) {
524         QModelIndex ix = makeTrackIndexFromID(trackId);
525         emit dataChanged(ix, ix, roles);
526         if (updateMultiTrack) {
527             emit trackVisibilityChanged();
528         }
529     }
530 }
531 
setTrackStackEnabled(int tid,bool enable)532 void TimelineItemModel::setTrackStackEnabled(int tid, bool enable)
533 {
534     std::shared_ptr<TrackModel> track = getTrackById(tid);
535     track->setEffectStackEnabled(enable);
536     QModelIndex ix = makeTrackIndexFromID(tid);
537     emit dataChanged(ix, ix, {TimelineModel::EffectsEnabledRole});
538 }
539 
importTrackEffects(int tid,std::weak_ptr<Mlt::Service> service)540 void TimelineItemModel::importTrackEffects(int tid, std::weak_ptr<Mlt::Service> service)
541 {
542     std::shared_ptr<TrackModel> track = getTrackById(tid);
543     std::shared_ptr<Mlt::Tractor> destination = track->getTrackService();
544     // Audio mixer effects are attached to the Tractor service, while track effects are attached to first playlist service
545     if (auto ptr = service.lock()) {
546         for (int i = 0; i < ptr->filter_count(); i++) {
547             std::unique_ptr<Mlt::Filter> filter(ptr->filter(i));
548             if (filter->get_int("internal_added") > 0) {
549                 destination->attach(*filter.get());
550             }
551         }
552     }
553 
554     track->importEffects(std::move(service));
555 }
556 
getTrackProperty(int tid,const QString & name) const557 QVariant TimelineItemModel::getTrackProperty(int tid, const QString &name) const
558 {
559     return getTrackById_const(tid)->getProperty(name);
560 }
561 
getFirstVideoTrackIndex() const562 int TimelineItemModel::getFirstVideoTrackIndex() const
563 {
564     int trackId = -1;
565     auto it = m_allTracks.cbegin();
566     while (it != m_allTracks.cend()) {
567         trackId = (*it)->getId();
568         if (!(*it)->isAudioTrack()) {
569             break;
570         }
571         ++it;
572     }
573     return trackId;
574 }
575 
getFirstAudioTrackIndex() const576 int TimelineItemModel::getFirstAudioTrackIndex() const
577 {
578     int trackId = -1;
579     auto it = m_allTracks.cbegin();
580     while (it != m_allTracks.cend()) {
581         if ((*it)->isAudioTrack()) {
582             trackId = (*it)->getId();
583         }
584         ++it;
585     }
586     return trackId;
587 }
588 
getTrackFullName(int tid) const589 const QString TimelineItemModel::getTrackFullName(int tid) const
590 {
591     QString tag = getTrackTagById(tid);
592     QString trackName = getTrackById_const(tid)->getProperty(QStringLiteral("kdenlive:track_name")).toString();
593     return trackName.isEmpty() ? tag : tag + QStringLiteral(" - ") + trackName;
594 }
595 
groupsData()596 const QString TimelineItemModel::groupsData()
597 {
598     return m_groups->toJson();
599 }
600 
loadGroups(const QString & groupsData)601 bool TimelineItemModel::loadGroups(const QString &groupsData)
602 {
603     return m_groups->fromJson(groupsData);
604 }
605 
notifyChange(const QModelIndex & topleft,const QModelIndex & bottomright,bool start,bool duration,bool updateThumb)606 void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, bool start, bool duration, bool updateThumb)
607 {
608     QVector<int> roles;
609     if (start) {
610         roles.push_back(TimelineModel::StartRole);
611         if (updateThumb) {
612             roles.push_back(TimelineModel::InPointRole);
613         }
614     }
615     if (duration) {
616         roles.push_back(TimelineModel::DurationRole);
617         if (updateThumb) {
618             roles.push_back(TimelineModel::OutPointRole);
619         }
620     }
621     emit dataChanged(topleft, bottomright, roles);
622 }
623 
notifyChange(const QModelIndex & topleft,const QModelIndex & bottomright,const QVector<int> & roles)624 void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, const QVector<int> &roles)
625 {
626     emit dataChanged(topleft, bottomright, roles);
627 }
628 
buildTrackCompositing(bool rebuild)629 void TimelineItemModel::buildTrackCompositing(bool rebuild)
630 {
631     bool isMultiTrack = pCore->enableMultiTrack(false);
632     auto it = m_allTracks.cbegin();
633     QScopedPointer<Mlt::Service> service(m_tractor->field());
634     QScopedPointer<Mlt::Field> field(m_tractor->field());
635     field->lock();
636     // Make sure all previous track compositing is removed
637     if (rebuild) {
638         while (service != nullptr && service->is_valid()) {
639             if (service->type() == mlt_service_transition_type) {
640                 Mlt::Transition t(mlt_transition(service->get_service()));
641                 service.reset(service->producer());
642                 if (t.get_int("internal_added") == 237) {
643                     // remove all compositing transitions
644                     field->disconnect_service(t);
645                     t.disconnect_all_producers();
646                 }
647             } else {
648                 service.reset(service->producer());
649             }
650         }
651     }
652     QString composite = TransitionsRepository::get()->getCompositingTransition();
653     bool hasMixer = pCore->mixer() != nullptr;
654     if (hasMixer) {
655         pCore->mixer()->cleanup();
656     }
657     while (it != m_allTracks.cend()) {
658         int trackPos = getTrackMltIndex((*it)->getId());
659         if (!composite.isEmpty() && !(*it)->isAudioTrack()) {
660             // video track, add composition
661             std::unique_ptr<Mlt::Transition> transition = TransitionsRepository::get()->getTransition(composite);
662             transition->set("internal_added", 237);
663             transition->set("always_active", 1);
664             transition->set_tracks(0, trackPos);
665             field->plant_transition(*transition.get(), 0, trackPos);
666         } else if ((*it)->isAudioTrack()) {
667             // audio mix
668             std::unique_ptr<Mlt::Transition> transition = TransitionsRepository::get()->getTransition(QStringLiteral("mix"));
669             transition->set("internal_added", 237);
670             transition->set("always_active", 1);
671             transition->set("accepts_blanks", 1);
672             transition->set("sum", 1);
673             transition->set_tracks(0, trackPos);
674             field->plant_transition(*transition.get(), 0, trackPos);
675             if (hasMixer) {
676                 pCore->mixer()->registerTrack((*it)->getId(), (*it)->getTrackService(), getTrackTagById((*it)->getId()), (*it)->getProperty(QStringLiteral("kdenlive:track_name")).toString());
677                 connect(pCore->mixer(), &MixerManager::showEffectStack, this, &TimelineItemModel::showTrackEffectStack);
678             }
679         }
680         ++it;
681     }
682     field->unlock();
683     if (isMultiTrack) {
684         pCore->enableMultiTrack(true);
685     }
686     if (composite.isEmpty()) {
687         pCore->displayMessage(i18n("Could not setup track compositing, check your install"), MessageType::ErrorMessage);
688     }
689 }
690 
notifyChange(const QModelIndex & topleft,const QModelIndex & bottomright,int role)691 void TimelineItemModel::notifyChange(const QModelIndex &topleft, const QModelIndex &bottomright, int role)
692 {
693     emit dataChanged(topleft, bottomright, {role});
694 }
695 
_beginRemoveRows(const QModelIndex & i,int j,int k)696 void TimelineItemModel::_beginRemoveRows(const QModelIndex &i, int j, int k)
697 {
698     // qDebug()<<"FORWARDING beginRemoveRows"<<i<<j<<k;
699     beginRemoveRows(i, j, k);
700 }
_beginInsertRows(const QModelIndex & i,int j,int k)701 void TimelineItemModel::_beginInsertRows(const QModelIndex &i, int j, int k)
702 {
703     // qDebug()<<"FORWARDING beginInsertRows"<<i<<j<<k;
704     beginInsertRows(i, j, k);
705 }
_endRemoveRows()706 void TimelineItemModel::_endRemoveRows()
707 {
708     // qDebug()<<"FORWARDING endRemoveRows";
709     endRemoveRows();
710 }
_endInsertRows()711 void TimelineItemModel::_endInsertRows()
712 {
713     // qDebug()<<"FORWARDING endinsertRows";
714     endInsertRows();
715 }
716 
_resetView()717 void TimelineItemModel::_resetView()
718 {
719     beginResetModel();
720     endResetModel();
721 }
722