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 ¤tName = 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