1 /*
2  * Copyright (c) 2013-2021 Meltytech, LLC
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "multitrackmodel.h"
19 #include "mltcontroller.h"
20 #include "mainwindow.h"
21 #include "settings.h"
22 #include "docks/playlistdock.h"
23 #include "util.h"
24 #include "audiolevelstask.h"
25 #include "shotcut_mlt_properties.h"
26 #include "controllers/filtercontroller.h"
27 #include "qmltypes/qmlmetadata.h"
28 #include "proxymanager.h"
29 #include "dialogs/longuitask.h"
30 
31 #include <QScopedPointer>
32 #include <QApplication>
33 #include <qmath.h>
34 #include <QTimer>
35 #include <QMessageBox>
36 
37 #include <Logger.h>
38 
39 static const quintptr NO_PARENT_ID = quintptr(-1);
40 static const char* kShotcutDefaultTransition = "lumaMix";
41 
MultitrackModel(QObject * parent)42 MultitrackModel::MultitrackModel(QObject *parent)
43     : QAbstractItemModel(parent)
44     , m_tractor(0)
45     , m_isMakingTransition(false)
46 {
47     connect(this, SIGNAL(modified()), SLOT(adjustBackgroundDuration()));
48     connect(this, SIGNAL(modified()), SLOT(adjustTrackFilters()));
49     connect(this, SIGNAL(reloadRequested()), SLOT(reload()), Qt::QueuedConnection);
50 }
51 
~MultitrackModel()52 MultitrackModel::~MultitrackModel()
53 {
54     delete m_tractor;
55     m_tractor = 0;
56 }
57 
rowCount(const QModelIndex & parent) const58 int MultitrackModel::rowCount(const QModelIndex &parent) const
59 {
60     if (!m_tractor)
61         return 0;
62     if (parent.isValid()) {
63         if (parent.internalId() != NO_PARENT_ID)
64             return 0;
65         int i = m_trackList.at(parent.row()).mlt_index;
66         QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
67         if (track) {
68             Mlt::Playlist playlist(*track);
69             int n = playlist.count();
70 //            LOG_DEBUG() << __FUNCTION__ << parent << i << n;
71             return n;
72         } else {
73             return 0;
74         }
75     }
76     return m_trackList.count();
77 }
78 
columnCount(const QModelIndex & parent) const79 int MultitrackModel::columnCount(const QModelIndex &parent) const
80 {
81     Q_UNUSED(parent);
82     return 1;
83 }
84 
data(const QModelIndex & index,int role) const85 QVariant MultitrackModel::data(const QModelIndex &index, int role) const
86 {
87     if (!m_tractor || !index.isValid())
88         return QVariant();
89     if (index.parent().isValid()) {
90         // Get data for a clip.
91         int i = m_trackList.at(index.internalId()).mlt_index;
92         QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
93         if (track) {
94             Mlt::Playlist playlist(*track);
95 //            LOG_DEBUG() << __FUNCTION__ << index.row();
96             QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(index.row()));
97             if (info)
98             switch (role) {
99             case NameRole: {
100                 QString result;
101                 if (info->producer && info->producer->is_valid()) {
102                     result = info->producer->get(kShotcutCaptionProperty);
103                     if (result.isNull()) {
104                         result = Util::baseName(ProxyManager::resource(*info->producer));
105                         if (!::qstrcmp(info->producer->get("mlt_service"), "timewarp")) {
106                             double speed = ::fabs(info->producer->get_double("warp_speed"));
107                             result = QString("%1 (%2x)").arg(result).arg(speed);
108                         }
109                     }
110                     if (result == "<producer>") {
111                         result = QString::fromUtf8(info->producer->get("mlt_service"));
112                     }
113                     if (info->producer->get_int(kIsProxyProperty)) {
114                         result.append("\n" + tr("(PROXY)"));
115                     }
116                 }
117                 return result;
118             }
119             case ResourceRole:
120             case Qt::DisplayRole: {
121                 QString result = QString::fromUtf8(info->resource);
122                 if (result == "<producer>" && info->producer
123                         && info->producer->is_valid() && info->producer->get("mlt_service"))
124                     result = QString::fromUtf8(info->producer->get("mlt_service"));
125                 return result;
126             }
127             case ServiceRole:
128                 if (info->producer && info->producer->is_valid())
129                     return QString::fromUtf8(info->producer->get("mlt_service"));
130                 break;
131             case IsBlankRole:
132                 return playlist.is_blank(index.row());
133             case StartRole:
134                 return info->start;
135             case DurationRole:
136                 return info->frame_count;
137             case InPointRole:
138                 return info->frame_in;
139             case OutPointRole:
140                 return info->frame_out;
141             case FramerateRole:
142                 return info->fps;
143             case IsAudioRole:
144                 return m_trackList[index.internalId()].type == AudioTrackType;
145             case AudioLevelsRole: {
146                 QVariant result;
147                 if (info->producer && info->producer->is_valid()) {
148                     info->producer->lock();
149                     if (info->producer->get_data(kAudioLevelsProperty)) {
150                         result = QVariant::fromValue(*((QVariantList*) info->producer->get_data(kAudioLevelsProperty)));
151                     }
152                     info->producer->unlock();
153                 }
154                 return result;
155             }
156             case FadeInRole: {
157                 QScopedPointer<Mlt::Filter> filter(getFilter("fadeInVolume", info->producer));
158                 if (!filter || !filter->is_valid())
159                     filter.reset(getFilter("fadeInBrightness", info->producer));
160                 if (!filter || !filter->is_valid())
161                     filter.reset(getFilter("fadeInMovit", info->producer));
162                 if (filter && filter->is_valid() && filter->get(kShotcutAnimInProperty))
163                     return filter->get_int(kShotcutAnimInProperty);
164                 else
165                     return (filter && filter->is_valid())? filter->get_length() : 0;
166             }
167             case FadeOutRole: {
168                 QScopedPointer<Mlt::Filter> filter(getFilter("fadeOutVolume", info->producer));
169                 if (!filter || !filter->is_valid())
170                     filter.reset(getFilter("fadeOutBrightness", info->producer));
171                 if (!filter || !filter->is_valid())
172                     filter.reset(getFilter("fadeOutMovit", info->producer));
173                 if (filter && filter->is_valid() && filter->get(kShotcutAnimOutProperty))
174                     return filter->get_int(kShotcutAnimOutProperty);
175                 else
176                     return (filter && filter->is_valid())? filter->get_length() : 0;
177             }
178             case IsTransitionRole:
179                 return isTransition(playlist, index.row());
180             case FileHashRole:
181                 return Util::getHash(*info->producer);
182             case SpeedRole: {
183                 double speed = 1.0;
184                 if (info->producer && info->producer->is_valid()) {
185                     if (!qstrcmp("timewarp", info->producer->get("mlt_service")))
186                         speed = info->producer->get_double("warp_speed");
187                 }
188                 return speed;
189             }
190             case IsFilteredRole:
191                 return isFiltered(info->producer);
192             case AudioIndexRole:
193                 return info->producer->get("audio_index");
194             default:
195                 break;
196             }
197         }
198     }
199     else {
200         // Get data for a track.
201         int i = m_trackList.at(index.row()).mlt_index;
202         QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
203         if (track) {
204             Mlt::Playlist playlist(*track);
205             switch (role) {
206             case NameRole:
207             case Qt::DisplayRole:
208                 return track->get(kTrackNameProperty);
209             case DurationRole:
210                 return playlist.get_playtime();
211             case IsMuteRole:
212                 return playlist.get_int("hide") & 2;
213             case IsHiddenRole:
214                 return playlist.get_int("hide") & 1;
215             case IsAudioRole:
216                 return m_trackList[index.row()].type == AudioTrackType;
217             case IsLockedRole:
218                 return track->get_int(kTrackLockProperty);
219             case IsCompositeRole: {
220                 QScopedPointer<Mlt::Transition> transition(getTransition("frei0r.cairoblend", i));
221                 if (!transition)
222                     transition.reset(getTransition("qtblend", i));
223                 if (!transition)
224                     transition.reset(getTransition("movit.overlay", i));
225                 if (transition && transition->is_valid()) {
226                     if (!transition->get_int("disable"))
227                         return true;
228                 }
229                 return false;
230             }
231             case IsFilteredRole:
232                 return isFiltered(track.data());
233             case IsBottomVideoRole:
234                 return m_trackList[index.row()].number == 0 && m_trackList[index.row()].type == VideoTrackType;
235             default:
236                 break;
237             }
238         }
239     }
240     return QVariant();
241 }
242 
index(int row,int column,const QModelIndex & parent) const243 QModelIndex MultitrackModel::index(int row, int column, const QModelIndex &parent) const
244 {
245     if (column > 0)
246         return QModelIndex();
247 //    LOG_DEBUG() << __FUNCTION__ << row << column << parent;
248     QModelIndex result;
249     if (parent.isValid()) {
250         int i = m_trackList.at(parent.row()).mlt_index;
251         QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
252         if (track) {
253             Mlt::Playlist playlist((mlt_playlist) track->get_producer());
254             if (row < playlist.count())
255                 result = createIndex(row, column, parent.row());
256         }
257     } else if (row < m_trackList.count()) {
258         result = createIndex(row, column, NO_PARENT_ID);
259     }
260     return result;
261 }
262 
makeIndex(int trackIndex,int clipIndex) const263 QModelIndex MultitrackModel::makeIndex(int trackIndex, int clipIndex) const
264 {
265     return index(clipIndex, 0, index(trackIndex));
266 }
267 
parent(const QModelIndex & index) const268 QModelIndex MultitrackModel::parent(const QModelIndex &index) const
269 {
270 //    LOG_DEBUG() << __FUNCTION__ << index;
271     if (!index.isValid() || index.internalId() == NO_PARENT_ID)
272         return QModelIndex();
273     else
274         return createIndex(index.internalId(), 0, NO_PARENT_ID);
275 }
276 
roleNames() const277 QHash<int, QByteArray> MultitrackModel::roleNames() const
278 {
279     QHash<int, QByteArray> roles;
280     roles[NameRole] = "name";
281     roles[ResourceRole] = "resource";
282     roles[ServiceRole] = "mlt_service";
283     roles[IsBlankRole] = "blank";
284     roles[StartRole] = "start";
285     roles[DurationRole] = "duration";
286     roles[InPointRole] = "in";
287     roles[OutPointRole] = "out";
288     roles[FramerateRole] = "fps";
289     roles[IsMuteRole] = "mute";
290     roles[IsHiddenRole] = "hidden";
291     roles[IsAudioRole] = "audio";
292     roles[AudioLevelsRole] = "audioLevels";
293     roles[IsCompositeRole] = "composite";
294     roles[IsLockedRole] = "locked";
295     roles[FadeInRole] = "fadeIn";
296     roles[FadeOutRole] = "fadeOut";
297     roles[IsTransitionRole] = "isTransition";
298     roles[FileHashRole] = "hash";
299     roles[SpeedRole] = "speed";
300     roles[IsFilteredRole] = "filtered";
301     roles[IsBottomVideoRole] = "isBottomVideo";
302     roles[AudioIndexRole] = "audioIndex";
303     return roles;
304 }
305 
setTrackName(int row,const QString & value)306 void MultitrackModel::setTrackName(int row, const QString &value)
307 {
308     if (row < m_trackList.size()) {
309         int i = m_trackList.at(row).mlt_index;
310         QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
311         if (track) {
312             track->set(kTrackNameProperty, value.toUtf8().constData());
313 
314             QModelIndex modelIndex = index(row, 0);
315             QVector<int> roles;
316             roles << NameRole;
317             emit dataChanged(modelIndex, modelIndex, roles);
318             emit modified();
319         }
320     }
321 }
322 
setTrackMute(int row,bool mute)323 void MultitrackModel::setTrackMute(int row, bool mute)
324 {
325     if (row < m_trackList.size()) {
326         int i = m_trackList.at(row).mlt_index;
327         QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
328         if (track) {
329             int hide = track->get_int("hide");
330             if (mute)
331                 hide |= 2;
332             else
333                 hide ^= 2;
334             track->set("hide", hide);
335 
336             QModelIndex modelIndex = index(row, 0);
337             QVector<int> roles;
338             roles << IsMuteRole;
339             emit dataChanged(modelIndex, modelIndex, roles);
340             emit modified();
341         }
342     }
343 }
344 
setTrackHidden(int row,bool hidden)345 void MultitrackModel::setTrackHidden(int row, bool hidden)
346 {
347     if (row < m_trackList.size()) {
348         int i = m_trackList.at(row).mlt_index;
349         QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
350         if (track) {
351             int hide = track->get_int("hide");
352             if (hidden)
353                 hide |= 1;
354             else
355                 hide ^= 1;
356             track->set("hide", hide);
357             MLT.refreshConsumer();
358 
359             QModelIndex modelIndex = index(row, 0);
360             QVector<int> roles;
361             roles << IsHiddenRole;
362             emit dataChanged(modelIndex, modelIndex, roles);
363             emit modified();
364         }
365     }
366 }
367 
setTrackComposite(int row,bool composite)368 void MultitrackModel::setTrackComposite(int row, bool composite)
369 {
370     if (row < m_trackList.size()) {
371         int i = m_trackList.at(row).mlt_index;
372         QScopedPointer<Mlt::Transition> transition(getTransition("frei0r.cairoblend", i));
373         if (transition) {
374             transition->set("disable", !composite);
375         } else {
376             transition.reset(getTransition("qtblend", i));
377             if (transition) {
378                 transition->set("disable", !composite);
379             } else {
380                 transition.reset(getTransition("movit.overlay", i));
381                 if (transition) {
382                     transition->set("disable", !composite);
383                 }
384             }
385         }
386         MLT.refreshConsumer();
387 
388         QModelIndex modelIndex = index(row, 0);
389         QVector<int> roles;
390         roles << IsCompositeRole;
391         emit dataChanged(modelIndex, modelIndex, roles);
392         emit modified();
393     }
394 }
395 
setTrackLock(int row,bool lock)396 void MultitrackModel::setTrackLock(int row, bool lock)
397 {
398     if (row < m_trackList.size()) {
399         int i = m_trackList.at(row).mlt_index;
400         QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
401         track->set(kTrackLockProperty, lock);
402 
403         QModelIndex modelIndex = index(row, 0);
404         QVector<int> roles;
405         roles << IsLockedRole;
406         emit dataChanged(modelIndex, modelIndex, roles);
407         emit modified();
408     }
409 }
410 
trimClipInValid(int trackIndex,int clipIndex,int delta,bool ripple)411 bool MultitrackModel::trimClipInValid(int trackIndex, int clipIndex, int delta, bool ripple)
412 {
413     bool result = true;
414     int i = m_trackList.at(trackIndex).mlt_index;
415     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
416     if (track) {
417         Mlt::Playlist playlist(*track);
418         QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(clipIndex));
419 
420         if (!info || (info->frame_in + delta) < 0 || (info->frame_in + delta) > info->frame_out)
421             result = false;
422         else if (delta < 0 && clipIndex <= 0)
423             result = false;
424         else if (!ripple && delta < 0 && clipIndex > 0 && !playlist.is_blank(clipIndex - 1))
425             result = false;
426         else if (!ripple && delta > 0 && clipIndex > 0 && isTransition(playlist, clipIndex - 1))
427             result = false;
428     }
429     return result;
430 }
431 
trimClipIn(int trackIndex,int clipIndex,int delta,bool ripple,bool rippleAllTracks)432 int MultitrackModel::trimClipIn(int trackIndex, int clipIndex, int delta, bool ripple, bool rippleAllTracks)
433 {
434     int result = clipIndex;
435     QList<int> otherTracksToRipple;
436     int otherTracksPosition = -1;
437 
438     for (int i = 0; i < m_trackList.count(); ++i) {
439         int mltIndex = m_trackList.at(i).mlt_index;
440         QScopedPointer<Mlt::Producer> track(m_tractor->track(mltIndex));
441         if (!track)
442             continue;
443 
444         //when not rippling, never touch the other tracks
445         if (trackIndex != i && (!ripple || !rippleAllTracks))
446             continue;
447 
448         if (rippleAllTracks) {
449             if (track->get_int(kTrackLockProperty))
450                 continue;
451 
452             if (trackIndex != i && ripple) {
453                 otherTracksToRipple << i;
454                 continue;
455             }
456         }
457 
458         Mlt::Playlist playlist(*track);
459         QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(clipIndex));
460         int filterIn = MLT.filterIn(playlist, clipIndex);
461         int filterOut = MLT.filterOut(playlist, clipIndex);
462 
463         Q_ASSERT(otherTracksPosition == -1);
464         otherTracksPosition = info->start;
465 
466         if (info->frame_in + delta < 0)
467              // clamp to clip start
468             delta = -info->frame_in;
469         if (playlist.is_blank(clipIndex - 1) && -delta > playlist.clip_length(clipIndex - 1))
470             // clamp to duration of blank space
471             delta = -playlist.clip_length(clipIndex - 1);
472 //        LOG_DEBUG() << "delta" << delta;
473 
474         playlist.resize_clip(clipIndex, info->frame_in + delta, info->frame_out);
475 
476         // Adjust filters.
477         adjustClipFilters(*info->producer, filterIn, filterOut, delta, 0);
478 
479         QModelIndex modelIndex = createIndex(clipIndex, 0, i);
480         QVector<int> roles;
481         roles << DurationRole;
482         roles << InPointRole;
483         emit dataChanged(modelIndex, modelIndex, roles);
484         AudioLevelsTask::start(*info->producer, this, modelIndex);
485 
486         if (!ripple) {
487             // Adjust left of the clip.
488             if (clipIndex > 0 && playlist.is_blank(clipIndex - 1)) {
489                 int out = playlist.clip_length(clipIndex - 1) + delta - 1;
490                 if (out < 0) {
491     //                LOG_DEBUG() << "remove blank at left";
492                     beginRemoveRows(index(i), clipIndex - 1, clipIndex - 1);
493                     playlist.remove(clipIndex - 1);
494                     endRemoveRows();
495                     --result;
496                 } else {
497     //                LOG_DEBUG() << "adjust blank on left to" << out;
498                     playlist.resize_clip(clipIndex - 1, 0, out);
499 
500                     QModelIndex index = createIndex(clipIndex - 1, 0, i);
501                     QVector<int> roles;
502                     roles << DurationRole;
503                     emit dataChanged(index, index, roles);
504                 }
505             } else if (delta > 0) {
506     //            LOG_DEBUG() << "add blank on left duration" << delta - 1;
507                 beginInsertRows(index(i), clipIndex, clipIndex);
508                 playlist.insert_blank(clipIndex, delta - 1);
509                 endInsertRows();
510                 ++result;
511             }
512         }
513         emit modified();
514     }
515     if (delta > 0) {
516         foreach (int idx, otherTracksToRipple) {
517             Q_ASSERT(otherTracksPosition != -1);
518             removeRegion(idx, otherTracksPosition, delta);
519         }
520     } else {
521         insertOrAdjustBlankAt(otherTracksToRipple, otherTracksPosition, -delta);
522     }
523     return result;
524 }
525 
notifyClipIn(int trackIndex,int clipIndex)526 void MultitrackModel::notifyClipIn(int trackIndex, int clipIndex)
527 {
528     if (trackIndex >= 0 && trackIndex < m_trackList.size() && clipIndex >= 0) {
529         QModelIndex index = createIndex(clipIndex, 0, trackIndex);
530         QVector<int> roles;
531         roles << AudioLevelsRole;
532         emit dataChanged(index, index, roles);
533         MLT.refreshConsumer();
534     }
535     m_isMakingTransition = false;
536 }
537 
trimClipOutValid(int trackIndex,int clipIndex,int delta,bool ripple)538 bool MultitrackModel::trimClipOutValid(int trackIndex, int clipIndex, int delta, bool ripple)
539 {
540     bool result = true;
541     int i = m_trackList.at(trackIndex).mlt_index;
542     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
543     if (track) {
544         Mlt::Playlist playlist(*track);
545         QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(clipIndex));
546         if (!info || (info->frame_out - delta) >= info->length || (info->frame_out - delta) < info->frame_in)
547             result = false;
548         else if (!ripple && delta < 0 && (clipIndex + 1) < playlist.count() && !playlist.is_blank(clipIndex + 1))
549             result = false;
550         else if (!ripple && delta > 0 && (clipIndex + 1) < playlist.count() && isTransition(playlist, clipIndex + 1))
551             return false;
552     }
553     return result;
554 }
555 
trackHeight() const556 int MultitrackModel::trackHeight() const
557 {
558     int result = m_tractor? m_tractor->get_int(kTrackHeightProperty) : Settings.timelineTrackHeight();
559     return result? result : qMax(10, Settings.timelineTrackHeight());
560 }
561 
setTrackHeight(int height)562 void MultitrackModel::setTrackHeight(int height)
563 {
564     if (m_tractor) {
565         Settings.setTimelineTrackHeight(height);
566         m_tractor->set(kTrackHeightProperty, Settings.timelineTrackHeight());
567         emit trackHeightChanged();
568     }
569 }
570 
scaleFactor() const571 double MultitrackModel::scaleFactor() const
572 {
573     double result = m_tractor? m_tractor->get_double(kTimelineScaleProperty) : 0;
574     return (result > 0)? result : (qPow(1.0, 3.0) + 0.01);
575 }
576 
setScaleFactor(double scale)577 void MultitrackModel::setScaleFactor(double scale)
578 {
579     if (m_tractor) {
580         m_tractor->set(kTimelineScaleProperty, scale);
581         emit scaleFactorChanged();
582     }
583 }
584 
trimClipOut(int trackIndex,int clipIndex,int delta,bool ripple,bool rippleAllTracks)585 int MultitrackModel::trimClipOut(int trackIndex, int clipIndex, int delta, bool ripple, bool rippleAllTracks)
586 {
587     QList<int> otherTracksToRipple;
588     int result = clipIndex;
589     int otherTracksPosition = -1;
590 
591     for (int i = 0; i < m_trackList.count(); ++i) {
592         int mltIndex = m_trackList.at(i).mlt_index;
593         QScopedPointer<Mlt::Producer> track(m_tractor->track(mltIndex));
594         if (!track)
595             continue;
596 
597         Mlt::Playlist playlist(*track);
598         QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(clipIndex));
599         int filterIn = MLT.filterIn(playlist, clipIndex);
600         int filterOut = MLT.filterOut(playlist, clipIndex);
601 
602         //when not rippling, never touch the other tracks
603         if (trackIndex != i && (!ripple || !rippleAllTracks))
604             continue;
605 
606         if (rippleAllTracks) {
607             if (track->get_int(kTrackLockProperty))
608                 continue;
609 
610             if (trackIndex != i && ripple) {
611                 otherTracksToRipple << i;
612                 continue;
613             }
614         }
615 
616         Q_ASSERT(otherTracksPosition == -1);
617         otherTracksPosition = info->start + info->frame_count - delta;
618 
619         if ((info->frame_out - delta) >= info->length)
620              // clamp to clip duration
621             delta = info->frame_out - info->length + 1;
622         if ((clipIndex + 1) < playlist.count() && playlist.is_blank(clipIndex + 1) && -delta > playlist.clip_length(clipIndex + 1))
623             delta = -playlist.clip_length(clipIndex + 1);
624 //        LOG_DEBUG() << "delta" << delta;
625 
626         if (!ripple) {
627             // Adjust right of the clip.
628             if (clipIndex >= 0 && (clipIndex + 1) < playlist.count() && playlist.is_blank(clipIndex + 1)) {
629                 int out = playlist.clip_length(clipIndex + 1) + delta - 1;
630                 if (out < 0) {
631     //                LOG_DEBUG() << "remove blank at right";
632                     beginRemoveRows(index(i), clipIndex + 1, clipIndex + 1);
633                     playlist.remove(clipIndex + 1);
634                     endRemoveRows();
635                 } else {
636     //                LOG_DEBUG() << "adjust blank on right to" << out;
637                     playlist.resize_clip(clipIndex + 1, 0, out);
638 
639                     QModelIndex index = createIndex(clipIndex + 1, 0, i);
640                     QVector<int> roles;
641                     roles << DurationRole;
642                     emit dataChanged(index, index, roles);
643                 }
644             } else if (delta > 0 && (clipIndex + 1) < playlist.count())  {
645                 // Add blank to right.
646     //            LOG_DEBUG() << "add blank on right duration" << (delta - 1);
647                 int newIndex = clipIndex + 1;
648                 beginInsertRows(index(i), newIndex, newIndex);
649                 playlist.insert_blank(newIndex, delta - 1);
650                 endInsertRows();
651             }
652         }
653         playlist.resize_clip(clipIndex, info->frame_in, info->frame_out - delta);
654 
655         // Adjust filters.
656         adjustClipFilters(*info->producer, filterIn, filterOut, 0, delta);
657 
658         QModelIndex index = createIndex(clipIndex, 0, i);
659         QVector<int> roles;
660         roles << DurationRole;
661         roles << OutPointRole;
662         emit dataChanged(index, index, roles);
663         AudioLevelsTask::start(*info->producer, this, index);
664         emit modified();
665     }
666     if (delta > 0) {
667         foreach (int idx, otherTracksToRipple) {
668             Q_ASSERT(otherTracksPosition != -1);
669             removeRegion(idx, otherTracksPosition, delta);
670         }
671     } else {
672         insertOrAdjustBlankAt(otherTracksToRipple, otherTracksPosition, -delta);
673     }
674     return result;
675 }
676 
notifyClipOut(int trackIndex,int clipIndex)677 void MultitrackModel::notifyClipOut(int trackIndex, int clipIndex)
678 {
679     if (trackIndex >= 0 && trackIndex < m_trackList.size() && clipIndex >= 0) {
680         QModelIndex index = createIndex(clipIndex, 0, trackIndex);
681         QVector<int> roles;
682         roles << AudioLevelsRole;
683         emit dataChanged(index, index, roles);
684         MLT.refreshConsumer();
685     }
686     m_isMakingTransition = false;
687 }
688 
moveClip(int fromTrack,int toTrack,int clipIndex,int position,bool ripple,bool rippleAllTracks)689 bool MultitrackModel::moveClip(int fromTrack, int toTrack, int clipIndex,
690                                int position, bool ripple, bool rippleAllTracks)
691 {
692 //    LOG_DEBUG() << __FUNCTION__ << clipIndex << "fromTrack" << fromTrack << "toTrack" << toTrack;
693     bool result = false;
694     int i = m_trackList.at(fromTrack).mlt_index;
695     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
696 
697     if (track) {
698         Mlt::Playlist playlist(*track);
699         QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(clipIndex));
700         QString xml = MLT.XML(info->producer);
701         Mlt::Producer clip(MLT.profile(), "xml-string", xml.toUtf8().constData());
702 
703         if (clip.is_valid()) {
704             clearMixReferences(fromTrack, clipIndex);
705             clip.set_in_and_out(info->frame_in, info->frame_out);
706 
707             if (ripple) {
708                 int targetIndex = playlist.get_clip_index_at(position);
709                 int length = playlist.clip_length(clipIndex);
710                 int targetIndexEnd = playlist.get_clip_index_at(position + length - 1);
711                 if ((clipIndex + 1) < playlist.count() && position >= playlist.get_playtime()) {
712                     // Clip relocated to end of playlist.
713                     moveClipToEnd(playlist, toTrack, clipIndex, position, ripple, rippleAllTracks);
714                     emit modified();
715                 }
716                 else if (fromTrack == toTrack && targetIndex >= clipIndex) {
717                     // Push the clips.
718                     int clipStart = playlist.clip_start(clipIndex);
719                     int duration = position - clipStart;
720                     QList<int> trackList;
721                     trackList << fromTrack;
722                     if (rippleAllTracks) {
723                         for (int i = 0; i < m_trackList.count(); ++i) {
724                             if (i == fromTrack)
725                                 continue;
726                             int mltIndex = m_trackList.at(i).mlt_index;
727                             QScopedPointer<Mlt::Producer> otherTrack(m_tractor->track(mltIndex));
728                             if (otherTrack && otherTrack->get_int(kTrackLockProperty))
729                                 continue;
730                             trackList << i;
731                         }
732                     }
733                     insertOrAdjustBlankAt(trackList, clipStart, duration);
734                     consolidateBlanks(playlist, fromTrack);
735                     emit modified();
736                 } else if (fromTrack == toTrack && (playlist.is_blank_at(position) || targetIndex == clipIndex) &&
737                           (playlist.is_blank_at(position + length - 1) || targetIndexEnd == clipIndex)) {
738                     // Reposition the clip within its current blank spot.
739                     moveClipInBlank(playlist, toTrack, clipIndex, position, ripple, rippleAllTracks);
740                     emit modified();
741                 } else {
742                     int clipPlaytime = clip.get_playtime();
743                     int clipStart = playlist.clip_start(clipIndex);
744 
745                     // Remove clip
746                     clearMixReferences(fromTrack, clipIndex);
747                     beginRemoveRows(index(fromTrack), clipIndex, clipIndex);
748                     playlist.remove(clipIndex);
749                     endRemoveRows();
750                     consolidateBlanks(playlist, fromTrack);
751 
752                     // Ripple delete on all unlocked tracks.
753                     if (clipPlaytime > 0 && rippleAllTracks)
754                     for (int j = 0; j < m_trackList.count(); ++j) {
755                         if (j == fromTrack)
756                             continue;
757 
758                         int mltIndex = m_trackList.at(j).mlt_index;
759                         QScopedPointer<Mlt::Producer> otherTrack(m_tractor->track(mltIndex));
760                         if (otherTrack) {
761                             if (otherTrack->get_int(kTrackLockProperty))
762                                 continue;
763 
764                             removeRegion(j, clipStart, clipPlaytime);
765                         }
766                     }
767                     consolidateBlanks(playlist, fromTrack);
768 
769                     // Insert clip
770                     insertClip(toTrack, clip, position, rippleAllTracks, false);
771                 }
772             } else {
773                 // Lift clip
774                 delete playlist.replace_with_blank(clipIndex);
775 
776                 QModelIndex index = createIndex(clipIndex, 0, fromTrack);
777                 QVector<int> roles;
778                 roles << ResourceRole;
779                 roles << ServiceRole;
780                 roles << IsBlankRole;
781                 roles << IsTransitionRole;
782                 emit dataChanged(index, index, roles);
783 
784                 consolidateBlanks(playlist, fromTrack);
785 
786                 // Overwrite with clip
787                 if (position + clip.get_playtime() >= 0)
788                     overwrite(toTrack, clip, position, false /* seek */);
789                 else
790                     emit modified();
791             }
792         }
793         result = true;
794     }
795     return result;
796 }
797 
overwriteClip(int trackIndex,Mlt::Producer & clip,int position,bool seek)798 int MultitrackModel::overwriteClip(int trackIndex, Mlt::Producer& clip, int position, bool seek)
799 {
800     createIfNeeded();
801     int result = -1;
802     int i = m_trackList.at(trackIndex).mlt_index;
803     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
804     if (track) {
805         Mlt::Playlist playlist(*track);
806         if (position >= playlist.get_playtime() - 1) {
807 //            LOG_DEBUG() << __FUNCTION__ << "appending";
808             removeBlankPlaceholder(playlist, trackIndex);
809             int n = playlist.count();
810             int length = position - playlist.clip_start(n - 1) - playlist.clip_length(n - 1);
811 
812             // Add blank to end if needed.
813             if (length > 0) {
814                 beginInsertRows(index(trackIndex), n, n);
815                 playlist.blank(length - 1);
816                 endInsertRows();
817                 ++n;
818             }
819 
820             // Append clip.
821             int in = clip.get_in();
822             int out = clip.get_out();
823             clip.set_in_and_out(0, clip.get_length() - 1);
824             beginInsertRows(index(trackIndex), n, n);
825             playlist.append(clip.parent(), in, out);
826             endInsertRows();
827             AudioLevelsTask::start(clip.parent(), this, createIndex(n, 0, trackIndex));
828             result = playlist.count() - 1;
829         } else if (position + clip.get_playtime() > playlist.get_playtime()
830             // Handle straddling - new clip larger than another with blanks on both sides.
831             || playlist.get_clip_index_at(position) == playlist.get_clip_index_at(position + clip.get_playtime() - 1)) {
832 //            LOG_DEBUG() << __FUNCTION__ << "overwriting blank space" << clip.get_playtime();
833             int targetIndex = playlist.get_clip_index_at(position);
834 
835             if (position > playlist.clip_start(targetIndex)) {
836                 splitClip(trackIndex, targetIndex, position);
837 
838                 // Notify item on left was adjusted.
839                 QModelIndex modelIndex = createIndex(targetIndex, 0, trackIndex);
840                 QVector<int> roles;
841                 roles << DurationRole;
842                 emit dataChanged(modelIndex, modelIndex, roles);
843                 AudioLevelsTask::start(clip.parent(), this, modelIndex);
844                 ++targetIndex;
845             } else if (position < 0) {
846                 clip.set_in_and_out(-position, clip.get_out());
847                 QModelIndex modelIndex = createIndex(targetIndex, 0, trackIndex);
848                 // Notify clip on right was adjusted.
849                 QVector<int> roles;
850                 roles << InPointRole;
851                 roles << DurationRole;
852                 emit dataChanged(modelIndex, modelIndex, roles);
853             }
854 
855             // Adjust clip on right.
856             int duration = playlist.clip_length(targetIndex) - clip.get_playtime();
857             if (duration > 0) {
858 //                LOG_DEBUG() << "adjust item on right" << (targetIndex) << " to" << duration;
859                 playlist.resize_clip(targetIndex, 0, duration - 1);
860                 QModelIndex modelIndex = createIndex(targetIndex, 0, trackIndex);
861                 // Notify clip on right was adjusted.
862                 QVector<int> roles;
863                 roles << DurationRole;
864                 emit dataChanged(modelIndex, modelIndex, roles);
865                 AudioLevelsTask::start(clip.parent(), this, modelIndex);
866             } else {
867 //                LOG_DEBUG() << "remove item on right";
868                 clearMixReferences(trackIndex, targetIndex);
869                 beginRemoveRows(index(trackIndex), targetIndex, targetIndex);
870                 playlist.remove(targetIndex);
871                 endRemoveRows();
872             }
873             // Insert clip between subclips.
874             int in = clip.get_in();
875             int out = clip.get_out();
876             clip.set_in_and_out(0, clip.get_length() - 1);
877             beginInsertRows(index(trackIndex), targetIndex, targetIndex);
878             playlist.insert(clip.parent(), targetIndex, in, out);
879             endInsertRows();
880             result = targetIndex;
881         }
882         if (result >= 0) {
883             QModelIndex index = createIndex(result, 0, trackIndex);
884             AudioLevelsTask::start(clip.parent(), this, index);
885             emit modified();
886             if (seek)
887                 emit seeked(playlist.clip_start(result) + playlist.clip_length(result));
888         }
889     }
890     return result;
891 }
892 
overwrite(int trackIndex,Mlt::Producer & clip,int position,bool seek)893 QString MultitrackModel::overwrite(int trackIndex, Mlt::Producer& clip, int position, bool seek)
894 {
895     createIfNeeded();
896     Mlt::Playlist result;
897     int i = m_trackList.at(trackIndex).mlt_index;
898     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
899     if (track) {
900         Mlt::Playlist playlist(*track);
901         removeBlankPlaceholder(playlist, trackIndex);
902         int targetIndex = playlist.get_clip_index_at(position);
903         if (position >= playlist.get_playtime() - 1) {
904 //            LOG_DEBUG() << __FUNCTION__ << "appending";
905             int n = playlist.count();
906             int length = position - playlist.clip_start(n - 1) - playlist.clip_length(n - 1);
907 
908             // Add blank to end if needed.
909             if (length > 0) {
910                 beginInsertRows(index(trackIndex), n, n);
911                 playlist.blank(length - 1);
912                 endInsertRows();
913                 ++n;
914             }
915 
916             // Append clip.
917             int in = clip.get_in();
918             int out = clip.get_out();
919             clip.set_in_and_out(0, clip.get_length() - 1);
920             beginInsertRows(index(trackIndex), n, n);
921             playlist.append(clip.parent(), in, out);
922             endInsertRows();
923             targetIndex = playlist.count() - 1;
924         } else {
925             int lastIndex = playlist.get_clip_index_at(position + clip.get_playtime());
926 //            LOG_DEBUG() << __FUNCTION__ << "overwriting with duration" << clip.get_playtime()
927 //                << "from" << targetIndex << "to" << lastIndex;
928 
929             // Add affected clips to result playlist.
930             int i = targetIndex;
931             if (position == playlist.clip_start(targetIndex))
932                 --i;
933             for (; i <= lastIndex; i++) {
934                 Mlt::Producer* producer = playlist.get_clip(i);
935                 if (producer)
936                     result.append(*producer);
937                 delete producer;
938             }
939 
940             if (position > playlist.clip_start(targetIndex)) {
941 //                LOG_DEBUG() << "split starting item" <<  targetIndex;
942                 splitClip(trackIndex, targetIndex, position);
943                 ++targetIndex;
944             } else if (position < 0) {
945                 clip.set_in_and_out(clip.get_in() - position, clip.get_out());
946                 position = 0;
947                 QModelIndex modelIndex = createIndex(targetIndex, 0, trackIndex);
948                 // Notify clip was adjusted.
949                 QVector<int> roles;
950                 roles << InPointRole;
951                 roles << DurationRole;
952                 emit dataChanged(modelIndex, modelIndex, roles);
953             }
954 
955             int length = clip.get_playtime();
956             while (length > 0 && targetIndex < playlist.count()) {
957                 if (playlist.clip_length(targetIndex) > length) {
958 //                    LOG_DEBUG() << "split last item" << targetIndex;
959                     splitClip(trackIndex, targetIndex, position + length);
960                 }
961 //                LOG_DEBUG() << "length" << length << "item length" << playlist.clip_length(targetIndex);
962                 length -= playlist.clip_length(targetIndex);
963 //                LOG_DEBUG() << "delete item" << targetIndex;
964                 clearMixReferences(trackIndex, targetIndex);
965                 beginRemoveRows(index(trackIndex), targetIndex, targetIndex);
966                 playlist.remove(targetIndex);
967                 endRemoveRows();
968             }
969 
970             // Insert clip between subclips.
971             int in = clip.get_in();
972             int out = clip.get_out();
973             clip.set_in_and_out(0, clip.get_length() - 1);
974             beginInsertRows(index(trackIndex), targetIndex, targetIndex);
975             playlist.insert(clip.parent(), targetIndex, in, out);
976             endInsertRows();
977         }
978         QModelIndex index = createIndex(targetIndex, 0, trackIndex);
979         AudioLevelsTask::start(clip.parent(), this, index);
980         emit overWritten(trackIndex, targetIndex);
981         emit modified();
982         emit seeked(playlist.clip_start(targetIndex) + playlist.clip_length(targetIndex), seek);
983     }
984     return MLT.XML(&result);
985 }
986 
insertClip(int trackIndex,Mlt::Producer & clip,int position,bool rippleAllTracks,bool seek)987 int MultitrackModel::insertClip(int trackIndex, Mlt::Producer &clip, int position, bool rippleAllTracks, bool seek)
988 {
989     createIfNeeded();
990     int result = -1;
991     int i = m_trackList.at(trackIndex).mlt_index;
992     int clipPlaytime = clip.get_playtime();
993     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
994     if (track) {
995         Mlt::Playlist playlist(*track);
996         if (position >= playlist.get_playtime() - 1) {
997 //            LOG_DEBUG() << __FUNCTION__ << "appending";
998             removeBlankPlaceholder(playlist, trackIndex);
999             int n = playlist.count();
1000             int length = position - playlist.clip_start(n - 1) - playlist.clip_length(n - 1);
1001 
1002             // Add blank to end if needed.
1003             if (length > 0) {
1004                 beginInsertRows(index(trackIndex), n, n);
1005                 playlist.blank(length - 1);
1006                 endInsertRows();
1007                 ++n;
1008             }
1009 
1010             // Append clip.
1011             int in = clip.get_in();
1012             int out = clip.get_out();
1013             clip.set_in_and_out(0, clip.get_length() - 1);
1014             beginInsertRows(index(trackIndex), n, n);
1015             playlist.append(clip.parent(), in, out);
1016             endInsertRows();
1017             result = playlist.count() - 1;
1018         } else {
1019 //            LOG_DEBUG() << __FUNCTION__ << "inserting" << position << MLT.XML(&clip);
1020             int targetIndex = playlist.get_clip_index_at(position);
1021 
1022             if (position > playlist.clip_start(targetIndex)) {
1023                 splitClip(trackIndex, targetIndex, position);
1024 
1025                 // Notify item on left was adjusted.
1026                 QModelIndex modelIndex = createIndex(targetIndex, 0, trackIndex);
1027                 QVector<int> roles;
1028                 roles << DurationRole;
1029                 emit dataChanged(modelIndex, modelIndex, roles);
1030                 AudioLevelsTask::start(clip.parent(), this, modelIndex);
1031                 ++targetIndex;
1032 
1033                 // Notify item on right was adjusted.
1034                 modelIndex = createIndex(targetIndex, 0, trackIndex);
1035                 emit dataChanged(modelIndex, modelIndex, roles);
1036                 AudioLevelsTask::start(clip.parent(), this, modelIndex);
1037             } else if (position < 0) {
1038                 clip.set_in_and_out(clip.get_in() - position, clip.get_out());
1039                 position = 0;
1040                 QModelIndex modelIndex = createIndex(targetIndex, 0, trackIndex);
1041                 // Notify clip was adjusted.
1042                 QVector<int> roles;
1043                 roles << InPointRole;
1044                 roles << DurationRole;
1045                 emit dataChanged(modelIndex, modelIndex, roles);
1046             }
1047 
1048             // Insert clip between split blanks.
1049             beginInsertRows(index(trackIndex), targetIndex, targetIndex);
1050             if (qstrcmp("blank", clip.get("mlt_service"))) {
1051                 int in = clip.get_in();
1052                 int out = clip.get_out();
1053                 clip.set_in_and_out(0, clip.get_length() - 1);
1054                 playlist.insert(clip.parent(), targetIndex, in, out);
1055             } else {
1056                 playlist.insert_blank(targetIndex, clipPlaytime - 1);
1057             }
1058             endInsertRows();
1059             result = targetIndex;
1060         }
1061         if (result >= 0) {
1062             if (rippleAllTracks) {
1063                 //fill in/expand blanks in all the other tracks
1064                 QList<int> tracksToInsertBlankInto;
1065                 for (int j = 0; j < m_trackList.count(); ++j) {
1066                     if (j == trackIndex)
1067                         continue;
1068                     int mltIndex = m_trackList.at(j).mlt_index;
1069                     QScopedPointer<Mlt::Producer> otherTrack(m_tractor->track(mltIndex));
1070                     if (otherTrack->get_int(kTrackLockProperty))
1071                         continue;
1072 
1073                     tracksToInsertBlankInto << j;
1074                 }
1075                 if (!tracksToInsertBlankInto.isEmpty())
1076                     insertOrAdjustBlankAt(tracksToInsertBlankInto, position, clipPlaytime);
1077             }
1078 
1079             QModelIndex index = createIndex(result, 0, trackIndex);
1080             AudioLevelsTask::start(clip.parent(), this, index);
1081             emit inserted(trackIndex, result);
1082             emit modified();
1083             emit seeked(playlist.clip_start(result) + playlist.clip_length(result), seek);
1084         }
1085     }
1086     return result;
1087 }
1088 
appendClip(int trackIndex,Mlt::Producer & clip)1089 int MultitrackModel::appendClip(int trackIndex, Mlt::Producer &clip)
1090 {
1091     if (!createIfNeeded()) {
1092         return -1;
1093     }
1094     int i = m_trackList.at(trackIndex).mlt_index;
1095     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1096     if (track) {
1097         Mlt::Playlist playlist(*track);
1098         removeBlankPlaceholder(playlist, trackIndex);
1099         i = playlist.count();
1100         int in = clip.get_in();
1101         int out = clip.get_out();
1102         clip.set_in_and_out(0, clip.get_length() - 1);
1103         beginInsertRows(index(trackIndex), i, i);
1104         playlist.append(clip.parent(), in, out);
1105         endInsertRows();
1106         QModelIndex index = createIndex(i, 0, trackIndex);
1107         AudioLevelsTask::start(clip.parent(), this, index);
1108         emit appended(trackIndex, i);
1109         emit modified();
1110         emit seeked(playlist.clip_start(i) + playlist.clip_length(i));
1111         return i;
1112     }
1113     return -1;
1114 }
1115 
removeClip(int trackIndex,int clipIndex,bool rippleAllTracks)1116 void MultitrackModel::removeClip(int trackIndex, int clipIndex, bool rippleAllTracks)
1117 {
1118     int i = m_trackList.at(trackIndex).mlt_index;
1119     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1120     int clipPlaytime = -1;
1121     int clipStart = -1;
1122 
1123     if (track) {
1124         Mlt::Playlist playlist(*track);
1125         if (clipIndex < playlist.count()) {
1126             // Shotcut does not like the behavior of remove() on a
1127             // transition (MLT mix clip). So, we null mlt_mix to prevent it.
1128             clearMixReferences(trackIndex, clipIndex);
1129 
1130             QScopedPointer<Mlt::Producer> producer(playlist.get_clip(clipIndex));
1131             if (producer) {
1132                 clipPlaytime = producer->get_playtime();
1133                 clipStart = playlist.clip_start(clipIndex);
1134             }
1135 
1136             beginRemoveRows(index(trackIndex), clipIndex, clipIndex);
1137             playlist.remove(clipIndex);
1138             endRemoveRows();
1139             consolidateBlanks(playlist, trackIndex);
1140 
1141             // Ripple all unlocked tracks.
1142             if (clipPlaytime > 0 && rippleAllTracks)
1143             for (int j = 0; j < m_trackList.count(); ++j) {
1144                 if (j == trackIndex)
1145                     continue;
1146 
1147                 int mltIndex = m_trackList.at(j).mlt_index;
1148                 QScopedPointer<Mlt::Producer> otherTrack(m_tractor->track(mltIndex));
1149                 if (otherTrack) {
1150                     if (otherTrack->get_int(kTrackLockProperty))
1151                         continue;
1152 
1153                     removeRegion(j, clipStart, clipPlaytime);
1154                 }
1155             }
1156             consolidateBlanks(playlist, trackIndex);
1157             emit modified();
1158         }
1159     }
1160 }
1161 
liftClip(int trackIndex,int clipIndex)1162 void MultitrackModel::liftClip(int trackIndex, int clipIndex)
1163 {
1164     int i = m_trackList.at(trackIndex).mlt_index;
1165     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1166     if (track) {
1167         Mlt::Playlist playlist(*track);
1168         if (clipIndex < playlist.count()) {
1169             // Shotcut does not like the behavior of replace_with_blank() on a
1170             // transition (MLT mix clip). So, we null mlt_mix to prevent it.
1171             clearMixReferences(trackIndex, clipIndex);
1172 
1173             delete playlist.replace_with_blank(clipIndex);
1174 
1175             QModelIndex index = createIndex(clipIndex, 0, trackIndex);
1176             QVector<int> roles;
1177             roles << ResourceRole;
1178             roles << ServiceRole;
1179             roles << IsBlankRole;
1180             roles << IsTransitionRole;
1181             emit dataChanged(index, index, roles);
1182 
1183             consolidateBlanks(playlist, trackIndex);
1184 
1185             emit modified();
1186         }
1187     }
1188 }
1189 
splitClip(int trackIndex,int clipIndex,int position)1190 void MultitrackModel::splitClip(int trackIndex, int clipIndex, int position)
1191 {
1192     int i = m_trackList.at(trackIndex).mlt_index;
1193     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1194     if (track) {
1195         Mlt::Playlist playlist(*track);
1196         QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(clipIndex));
1197 
1198         // Make copy of clip.
1199         Mlt::Producer producer(MLT.profile(), "xml-string",
1200             MLT.XML(info->producer).toUtf8().constData());
1201         int in = info->frame_in;
1202         int out = info->frame_out;
1203         int filterIn = MLT.filterIn(playlist, clipIndex);
1204         int filterOut = MLT.filterOut(playlist, clipIndex);
1205         int duration = position - playlist.clip_start(clipIndex);
1206         int delta = info->frame_count - duration;
1207 
1208         // Connect a transition on the left to the new producer.
1209         if (isTransition(playlist, clipIndex - 1) && !playlist.is_blank(clipIndex)) {
1210             QScopedPointer<Mlt::Producer> p(playlist.get_clip(clipIndex - 1));
1211             Mlt::Tractor tractor(p->parent());
1212             if (tractor.is_valid()) {
1213                 QScopedPointer<Mlt::Producer> track_b(tractor.track(1));
1214                 track_b.reset(producer.cut(track_b->get_in(), track_b->get_out()));
1215                 tractor.set_track(*track_b, 1);
1216             }
1217         }
1218 
1219         // Remove fades that are usually not desired after split.
1220         QScopedPointer<Mlt::Filter> filter(getFilter("fadeOutVolume", &producer));
1221         if (filter && filter->is_valid())
1222             producer.detach(*filter);
1223         filter.reset(getFilter("fadeOutBrightness", &producer));
1224         if (filter && filter->is_valid())
1225             producer.detach(*filter);
1226         filter.reset(getFilter("fadeOutMovit", &producer));
1227         if (filter && filter->is_valid())
1228             producer.detach(*filter);
1229         filter.reset(getFilter("fadeInVolume", info->producer));
1230         if (filter && filter->is_valid())
1231             info->producer->detach(*filter);
1232         filter.reset(getFilter("fadeInBrightness", info->producer));
1233         if (filter && filter->is_valid())
1234             info->producer->detach(*filter);
1235         filter.reset(getFilter("fadeInMovit", info->producer));
1236         if (filter && filter->is_valid())
1237             info->producer->detach(*filter);
1238 
1239         beginInsertRows(index(trackIndex), clipIndex, clipIndex);
1240         if (playlist.is_blank(clipIndex)) {
1241             playlist.insert_blank(clipIndex, duration - 1);
1242         } else {
1243             playlist.insert(producer, clipIndex, in, in + duration - 1);
1244             QModelIndex modelIndex = createIndex(clipIndex, 0, trackIndex);
1245             AudioLevelsTask::start(producer.parent(), this, modelIndex);
1246         }
1247         endInsertRows();
1248 
1249         adjustClipFilters(producer, filterIn, out, 0, delta);
1250 
1251         playlist.resize_clip(clipIndex + 1, in + duration, out);
1252         QModelIndex modelIndex = createIndex(clipIndex + 1, 0, trackIndex);
1253         QVector<int> roles;
1254         roles << DurationRole;
1255         roles << InPointRole;
1256         roles << FadeInRole;
1257         emit dataChanged(modelIndex, modelIndex, roles);
1258         AudioLevelsTask::start(*info->producer, this, modelIndex);
1259 
1260         delta = duration;
1261         adjustClipFilters(*info->producer, in, filterOut, delta, 0);
1262 
1263         emit modified();
1264     }
1265 }
1266 
joinClips(int trackIndex,int clipIndex)1267 void MultitrackModel::joinClips(int trackIndex, int clipIndex)
1268 {
1269     if (clipIndex < 0) return;
1270     int i = m_trackList.at(trackIndex).mlt_index;
1271     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1272     if (track) {
1273         Mlt::Playlist playlist(*track);
1274         if (clipIndex >= playlist.count() - 1) return;
1275         QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(clipIndex));
1276         int in = info->frame_in;
1277         int out = info->frame_out;
1278         int delta = -playlist.clip_length(clipIndex + 1);
1279 
1280         // Move a fade out on the right clip onto the left clip.
1281         QScopedPointer<Mlt::Producer> clip(playlist.get_clip(clipIndex));
1282         info.reset(playlist.clip_info(clipIndex + 1));
1283         QScopedPointer<Mlt::Filter> filter(getFilter("fadeOutVolume", info->producer));
1284         if (filter && filter->is_valid())
1285             clip->parent().attach(*filter);
1286         filter.reset(getFilter("fadeOutBrightness", info->producer));
1287         if (filter && filter->is_valid())
1288             clip->parent().attach(*filter);
1289         filter.reset(getFilter("fadeOutMovit", info->producer));
1290         if (filter && filter->is_valid())
1291             clip->parent().attach(*filter);
1292 
1293         playlist.resize_clip(clipIndex, in, out - delta);
1294         QModelIndex modelIndex = createIndex(clipIndex, 0, trackIndex);
1295         QVector<int> roles;
1296         roles << DurationRole;
1297         roles << OutPointRole;
1298         roles << FadeOutRole;
1299         emit dataChanged(modelIndex, modelIndex, roles);
1300         AudioLevelsTask::start(clip->parent(), this, modelIndex);
1301 
1302         clearMixReferences(trackIndex, clipIndex + 1);
1303         beginRemoveRows(index(trackIndex), clipIndex + 1, clipIndex + 1);
1304         playlist.remove(clipIndex + 1);
1305         endRemoveRows();
1306 
1307         adjustClipFilters(clip->parent(), in, out, 0, delta);
1308 
1309         emit modified();
1310     }
1311 }
1312 
fadeIn(int trackIndex,int clipIndex,int duration)1313 void MultitrackModel::fadeIn(int trackIndex, int clipIndex, int duration)
1314 {
1315     int i = m_trackList.at(trackIndex).mlt_index;
1316     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1317     if (track) {
1318         Mlt::Playlist playlist(*track);
1319         QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(clipIndex));
1320         if (info && info->producer && info->producer->is_valid()) {
1321             bool isChanged = false;
1322             QScopedPointer<Mlt::Filter> filter;
1323             duration = qBound(0, duration, info->frame_count);
1324 
1325             if (m_trackList[trackIndex].type == VideoTrackType
1326                 // If video track index is not None.
1327                 && (!info->producer->get("video_index") || info->producer->get_int("video_index") != -1)) {
1328 
1329                 // Get video filter.
1330                 if (Settings.playerGPU())
1331                     filter.reset(getFilter("fadeInMovit", info->producer));
1332                 else
1333                     filter.reset(getFilter("fadeInBrightness", info->producer));
1334 
1335                 if (duration > 0) {
1336                     // Add video filter if needed.
1337                     if (!filter) {
1338                         if (Settings.playerGPU()) {
1339                             Mlt::Filter f(MLT.profile(), "movit.opacity");
1340                             f.set(kShotcutFilterProperty, "fadeInMovit");
1341                             f.set("alpha", 1);
1342                             info->producer->attach(f);
1343                             filter.reset(new Mlt::Filter(f));
1344                         } else {
1345                             Mlt::Filter f(MLT.profile(), "brightness");
1346                             f.set(kShotcutFilterProperty, "fadeInBrightness");
1347                             f.set("alpha", 1);
1348                             info->producer->attach(f);
1349                             filter.reset(new Mlt::Filter(f));
1350                         }
1351                         filter->set_in_and_out(info->frame_in, info->frame_out);
1352                         emit filterOutChanged(info->frame_out, filter.data());
1353                     }
1354 
1355                     // Adjust video filter.
1356                     if (Settings.playerGPU()) {
1357                         // Special handling for animation keyframes on movit.opacity.
1358                         filter->clear("opacity");
1359                         filter->anim_set("opacity", 0, 0, 0, mlt_keyframe_smooth);
1360                         filter->anim_set("opacity", 1, duration - 1);
1361                     } else {
1362                         // Special handling for animation keyframes on brightness.
1363                         const char* key = filter->get_int("alpha") != 1? "alpha" : "level";
1364                         filter->clear(key);
1365                         filter->anim_set(key, 0, 0);
1366                         filter->anim_set(key, 1, duration - 1);
1367                     }
1368                     filter->set(kShotcutAnimInProperty, duration);
1369                     isChanged = true;
1370                 } else if (filter) {
1371                     // Remove the video filter.
1372                     info->producer->detach(*filter);
1373                     emit filterAddedOrRemoved(info->producer);
1374                     filter->set(kShotcutAnimInProperty, duration);
1375                     isChanged = true;
1376                 }
1377             }
1378 
1379             // If audio track index is not None.
1380             if (!info->producer->get("audio_index") || info->producer->get_int("audio_index") != -1) {
1381                 // Get audio filter.
1382                 filter.reset(getFilter("fadeInVolume", info->producer));
1383 
1384                 if (duration > 0) {
1385                     // Add audio filter if needed.
1386                     if (!filter) {
1387                         Mlt::Filter f(MLT.profile(), "volume");
1388                         f.set(kShotcutFilterProperty, "fadeInVolume");
1389                         info->producer->attach(f);
1390                         filter.reset(new Mlt::Filter(f));
1391                         filter->set_in_and_out(info->frame_in, info->frame_out);
1392                         emit filterOutChanged(info->frame_out, filter.data());
1393                     }
1394 
1395                     // Adjust audio filter.
1396                     filter->clear("level");
1397                     filter->anim_set("level", -60, 0);
1398                     filter->anim_set("level", 0, duration - 1);
1399                     filter->set(kShotcutAnimInProperty, duration);
1400                     isChanged = true;
1401                 } else if (filter) {
1402                     // Remove the audio filter.
1403                     info->producer->detach(*filter);
1404                     emit filterAddedOrRemoved(info->producer);
1405                     filter->set(kShotcutAnimInProperty, duration);
1406                     isChanged = true;
1407                 }
1408             }
1409 
1410             if (isChanged) {
1411                  // Signal change.
1412                 QModelIndex modelIndex = createIndex(clipIndex, 0, trackIndex);
1413                 QVector<int> roles;
1414                 roles << FadeInRole;
1415                 emit dataChanged(modelIndex, modelIndex, roles);
1416                 emit modified();
1417             }
1418         }
1419     }
1420 }
1421 
fadeOut(int trackIndex,int clipIndex,int duration)1422 void MultitrackModel::fadeOut(int trackIndex, int clipIndex, int duration)
1423 {
1424     int i = m_trackList.at(trackIndex).mlt_index;
1425     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1426     if (track) {
1427         Mlt::Playlist playlist(*track);
1428         QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(clipIndex));
1429         if (info && info->producer && info->producer->is_valid()) {
1430             QScopedPointer<Mlt::Filter> filter;
1431             duration = qBound(0, duration, info->frame_count);
1432             bool isChanged = false;
1433 
1434             if (m_trackList[trackIndex].type == VideoTrackType
1435                 // If video track index is not None.
1436                 && (!info->producer->get("video_index") || info->producer->get_int("video_index") != -1)) {
1437 
1438                 // Get video filter.
1439                 if (Settings.playerGPU())
1440                     filter.reset(getFilter("fadeOutMovit", info->producer));
1441                 else
1442                     filter.reset(getFilter("fadeOutBrightness", info->producer));
1443 
1444                 if (duration > 0) {
1445                     // Add video filter if needed.
1446                     if (!filter) {
1447                         if (Settings.playerGPU()) {
1448                             Mlt::Filter f(MLT.profile(), "movit.opacity");
1449                             f.set(kShotcutFilterProperty, "fadeOutMovit");
1450                             f.set("alpha", 1);
1451                             info->producer->attach(f);
1452                             filter.reset(new Mlt::Filter(f));
1453                         } else {
1454                             Mlt::Filter f(MLT.profile(), "brightness");
1455                             f.set(kShotcutFilterProperty, "fadeOutBrightness");
1456                             f.set("alpha", 1);
1457                             info->producer->attach(f);
1458                             filter.reset(new Mlt::Filter(f));
1459                         }
1460                         filter->set_in_and_out(info->frame_in, info->frame_out);
1461                         emit filterOutChanged(info->frame_out, filter.data());
1462                     }
1463 
1464                     // Adjust video filter.
1465                     if (Settings.playerGPU()) {
1466                         // Special handling for animation keyframes on movit.opacity.
1467                         filter->clear("opacity");
1468                         filter->anim_set("opacity", 1, info->frame_count - duration, 0, mlt_keyframe_smooth);
1469                         filter->anim_set("opacity", 0, info->frame_count - 1);
1470                     } else {
1471                         // Special handling for animation keyframes on brightness.
1472                         const char* key = filter->get_int("alpha") != 1? "alpha" : "level";
1473                         filter->clear(key);
1474                         filter->anim_set(key, 1, info->frame_count - duration);
1475                         filter->anim_set(key, 0, info->frame_count - 1);
1476                     }
1477                     filter->set(kShotcutAnimOutProperty, duration);
1478                     isChanged = true;
1479                 } else if (filter) {
1480                     // Remove the video filter.
1481                     info->producer->detach(*filter);
1482                     emit filterAddedOrRemoved(info->producer);
1483                     filter->set(kShotcutAnimOutProperty, duration);
1484                     isChanged = true;
1485                 }
1486             }
1487 
1488             // If audio track index is not None.
1489             if (!info->producer->get("audio_index") || info->producer->get_int("audio_index") != -1) {
1490                 // Get audio filter.
1491                 filter.reset(getFilter("fadeOutVolume", info->producer));
1492 
1493                 if (duration > 0) {
1494                     // Add audio filter if needed.
1495                     if (!filter) {
1496                         Mlt::Filter f(MLT.profile(), "volume");
1497                         f.set(kShotcutFilterProperty, "fadeOutVolume");
1498                         info->producer->attach(f);
1499                         filter.reset(new Mlt::Filter(f));
1500                         filter->set_in_and_out(info->frame_in, info->frame_out);
1501                         emit filterOutChanged(info->frame_out, filter.data());
1502                     }
1503 
1504                     // Adjust audio filter.
1505                     filter->clear("level");
1506                     filter->anim_set("level", 0, info->frame_count - duration);
1507                     filter->anim_set("level", -60, info->frame_count - 1);
1508                     filter->set(kShotcutAnimOutProperty, duration);
1509                     isChanged = true;
1510                 } else if (filter) {
1511                     // Remove the audio filter.
1512                     info->producer->detach(*filter);
1513                     emit filterAddedOrRemoved(info->producer);
1514                     filter->set(kShotcutAnimOutProperty, duration);
1515                     isChanged = true;
1516                 }
1517             }
1518 
1519             if (isChanged) {
1520                  // Signal change.
1521                 QModelIndex modelIndex = createIndex(clipIndex, 0, trackIndex);
1522                 QVector<int> roles;
1523                 roles << FadeOutRole;
1524                 emit dataChanged(modelIndex, modelIndex, roles);
1525                 emit modified();
1526             }
1527         }
1528     }
1529 }
1530 
addTransitionValid(int fromTrack,int toTrack,int clipIndex,int position,bool ripple)1531 bool MultitrackModel::addTransitionValid(int fromTrack, int toTrack, int clipIndex, int position, bool ripple)
1532 {
1533     bool result = false;
1534     int i = m_trackList.at(toTrack).mlt_index;
1535     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1536     if (track) {
1537         Mlt::Playlist playlist(*track);
1538         if (fromTrack == toTrack) {
1539             int targetIndex = playlist.get_clip_index_at(position);
1540             int previousIndex = clipIndex - 1 - (playlist.is_blank(clipIndex - 1)? 1 : 0);
1541             int nextIndex = clipIndex + 1 + (playlist.is_blank(clipIndex + 1)? 1 : 0);
1542             int endOfPreviousClip = playlist.clip_start(previousIndex) + playlist.clip_length(previousIndex) - 1;
1543             int endOfCurrentClip = position + playlist.clip_length(clipIndex) - 1;
1544             int startOfNextClip = playlist.clip_start(nextIndex);
1545             auto isBlankAtPosition = playlist.is_blank_at(position);
1546             auto isTransitionAtPreviousIndex = isTransition(playlist, previousIndex);
1547             auto isBlankAtEndOfCurrentClip = playlist.is_blank_at(endOfCurrentClip);
1548             auto isTransitionAtNextIndex = isTransition(playlist, nextIndex);
1549 
1550             if ((targetIndex < clipIndex && (endOfCurrentClip > endOfPreviousClip) && (position > playlist.clip_start(previousIndex)) && !isBlankAtPosition && !isTransitionAtPreviousIndex)
1551                     ||
1552                 (!ripple && (targetIndex >= clipIndex) && (position < startOfNextClip) && !isBlankAtEndOfCurrentClip && !isTransitionAtNextIndex)) {
1553                 result = true;
1554             }
1555         }
1556     }
1557     return result;
1558 }
1559 
addTransition(int trackIndex,int clipIndex,int position,bool ripple,bool rippleAllTracks)1560 int MultitrackModel::addTransition(int trackIndex, int clipIndex, int position, bool ripple, bool rippleAllTracks)
1561 {
1562     int i = m_trackList.at(trackIndex).mlt_index;
1563     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1564     if (track) {
1565         Mlt::Playlist playlist(*track);
1566         int targetIndex = playlist.get_clip_index_at(position);
1567         int previousIndex = clipIndex - 1 - (playlist.is_blank(clipIndex - 1)? 1 : 0);
1568         int nextIndex = clipIndex + 1 + (playlist.is_blank(clipIndex + 1)? 1 : 0);
1569         int endOfPreviousClip = playlist.clip_start(previousIndex) + playlist.clip_length(previousIndex) - 1;
1570         int endOfCurrentClip = position + playlist.clip_length(clipIndex) - 1;
1571         int startOfNextClip = playlist.clip_start(nextIndex);
1572 
1573         if ((targetIndex < clipIndex && endOfCurrentClip > endOfPreviousClip) || // dragged left
1574             (targetIndex >= clipIndex && position < startOfNextClip)) { // dragged right
1575             int duration = qAbs(position - playlist.clip_start(clipIndex));
1576 
1577             // Remove a blank duration from the transition duration.
1578             if (playlist.is_blank(clipIndex - 1) && targetIndex < clipIndex)
1579                 duration -= playlist.clip_length(clipIndex - 1);
1580             else if (playlist.is_blank(clipIndex + 1) && targetIndex >= clipIndex)
1581                 duration -= playlist.clip_length(clipIndex + 1);
1582 
1583             // Adjust/insert blanks
1584             moveClipInBlank(playlist, trackIndex, clipIndex, position, ripple, rippleAllTracks, duration);
1585             targetIndex = playlist.get_clip_index_at(position);
1586 
1587             // Create mix
1588             beginInsertRows(index(trackIndex), targetIndex + 1, targetIndex + 1);
1589             playlist.mix(targetIndex, duration);
1590             Mlt::Producer producer(playlist.get_clip(targetIndex + 1));
1591             if (producer.is_valid()) {
1592                 producer.parent().set(kShotcutTransitionProperty, kShotcutDefaultTransition);
1593             }
1594             endInsertRows();
1595 
1596             // Add transitions
1597             Mlt::Transition dissolve(MLT.profile(), Settings.playerGPU()? "movit.luma_mix" : "luma");
1598             Mlt::Transition crossFade(MLT.profile(), "mix:-1");
1599             if (!Settings.playerGPU()) dissolve.set("alpha_over", 1);
1600             playlist.mix_add(targetIndex + 1, &dissolve);
1601             playlist.mix_add(targetIndex + 1, &crossFade);
1602 
1603             // Notify ins and outs changed
1604             QModelIndex modelIndex = createIndex(targetIndex, 0, trackIndex);
1605             QVector<int> roles;
1606             roles << StartRole;
1607             roles << OutPointRole;
1608             roles << DurationRole;
1609             emit dataChanged(modelIndex, modelIndex, roles);
1610             modelIndex = createIndex(targetIndex + 2, 0, trackIndex);
1611             roles.clear();
1612             roles << StartRole;
1613             roles << InPointRole;
1614             roles << DurationRole;
1615             roles << AudioLevelsRole;
1616             emit dataChanged(modelIndex, modelIndex, roles);
1617             emit modified();
1618             return targetIndex + 1;
1619         }
1620     }
1621     return -1;
1622 }
1623 
clearMixReferences(int trackIndex,int clipIndex)1624 void MultitrackModel::clearMixReferences(int trackIndex, int clipIndex)
1625 {
1626     int i = m_trackList.at(trackIndex).mlt_index;
1627     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1628     if (track) {
1629         Mlt::Playlist playlist(*track);
1630         QScopedPointer<Mlt::Producer> producer(playlist.get_clip(clipIndex - 1));
1631         if (producer && producer->is_valid()) {
1632             // Clear these since they are no longer valid.
1633             producer->set("mix_in", NULL, 0);
1634             producer->set("mix_out", NULL, 0);
1635             producer.reset(playlist.get_clip(clipIndex));
1636             if (producer && producer->is_valid()) {
1637                 producer->parent().set("mlt_mix", NULL, 0);
1638                 producer->set("mix_in", NULL, 0);
1639                 producer->set("mix_out", NULL, 0);
1640             }
1641             producer.reset(playlist.get_clip(clipIndex + 1));
1642             if (producer && producer->is_valid()) {
1643                 producer->set("mix_in", NULL, 0);
1644                 producer->set("mix_out", NULL, 0);
1645             }
1646         }
1647     }
1648 }
1649 
removeTransition(int trackIndex,int clipIndex)1650 void MultitrackModel::removeTransition(int trackIndex, int clipIndex)
1651 {
1652     int i = m_trackList.at(trackIndex).mlt_index;
1653     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1654     if (track) {
1655         Mlt::Playlist playlist(*track);
1656         clearMixReferences(trackIndex, clipIndex);
1657         beginRemoveRows(index(trackIndex), clipIndex, clipIndex);
1658         playlist.remove(clipIndex);
1659         endRemoveRows();
1660         --clipIndex;
1661 
1662         QModelIndex modelIndex = createIndex(clipIndex, 0, trackIndex);
1663         QVector<int> roles;
1664         roles << OutPointRole;
1665         roles << DurationRole;
1666         emit dataChanged(modelIndex, modelIndex, roles);
1667         modelIndex = createIndex(clipIndex + 1, 0, trackIndex);
1668         roles << InPointRole;
1669         roles << DurationRole;
1670         emit dataChanged(modelIndex, modelIndex, roles);
1671         emit modified();
1672     }
1673 }
1674 
removeTransitionByTrimIn(int trackIndex,int clipIndex,int delta)1675 void MultitrackModel::removeTransitionByTrimIn(int trackIndex, int clipIndex, int delta)
1676 {
1677     QModelIndex modelIndex = index(clipIndex, 0, index(trackIndex));
1678     clearMixReferences(trackIndex, clipIndex);
1679     int duration = -data(modelIndex, MultitrackModel::DurationRole).toInt();
1680     liftClip(trackIndex, clipIndex);
1681     trimClipOut(trackIndex, clipIndex - 1, duration, false, false);
1682     notifyClipOut(trackIndex, clipIndex - 1);
1683     if (delta) {
1684         trimClipIn(trackIndex, clipIndex, delta, false, false);
1685         notifyClipIn(trackIndex, clipIndex);
1686     }
1687 }
1688 
removeTransitionByTrimOut(int trackIndex,int clipIndex,int delta)1689 void MultitrackModel::removeTransitionByTrimOut(int trackIndex, int clipIndex, int delta)
1690 {
1691     QModelIndex modelIndex = index(clipIndex + 1, 0, index(trackIndex));
1692     clearMixReferences(trackIndex, clipIndex);
1693     int duration = -data(modelIndex, MultitrackModel::DurationRole).toInt();
1694     liftClip(trackIndex, clipIndex + 1);
1695     trimClipIn(trackIndex, clipIndex + 2, duration, false, false);
1696     notifyClipIn(trackIndex, clipIndex + 1);
1697     if (delta) {
1698         trimClipOut(trackIndex, clipIndex, delta, false, false);
1699         notifyClipOut(trackIndex, clipIndex);
1700     }
1701 }
1702 
trimTransitionInValid(int trackIndex,int clipIndex,int delta)1703 bool MultitrackModel::trimTransitionInValid(int trackIndex, int clipIndex, int delta)
1704 {
1705     if (m_isMakingTransition) return false;
1706     bool result = false;
1707     int i = m_trackList.at(trackIndex).mlt_index;
1708     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1709     if (track) {
1710         Mlt::Playlist playlist(*track);
1711         if (clipIndex + 2 < playlist.count()) {
1712             Mlt::ClipInfo info;
1713             // Check if there is already a transition and its new length valid.
1714             if (isTransition(playlist, clipIndex + 1) && playlist.clip_length(clipIndex + 1) + delta > 0) {
1715                 // Check clip A out point.
1716                 playlist.clip_info(clipIndex, &info);
1717                 info.frame_out -= delta;
1718                 if (info.frame_out > info.frame_in && info.frame_out < info.length) {
1719                     // Check clip B in point.
1720                     playlist.clip_info(clipIndex + 2, &info);
1721                     info.frame_in -= playlist.clip_length(clipIndex + 1) + delta;
1722                     if (info.frame_in >= 0 && info.frame_in <= info.frame_out)
1723                         result = true;
1724                 }
1725             }
1726         }
1727     }
1728     return result;
1729 }
1730 
trimTransitionIn(int trackIndex,int clipIndex,int delta)1731 void MultitrackModel::trimTransitionIn(int trackIndex, int clipIndex, int delta)
1732 {
1733 //    LOG_DEBUG() << "clipIndex" << clipIndex << "delta" << delta;
1734     int i = m_trackList.at(trackIndex).mlt_index;
1735     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1736     if (track) {
1737         Mlt::Playlist playlist(*track);
1738 
1739         // Adjust the playlist "mix" entry.
1740         QScopedPointer<Mlt::Producer> producer(playlist.get_clip(clipIndex + 1));
1741         Mlt::Tractor tractor(producer->parent());
1742         if (!tractor.is_valid())
1743             return;
1744         QScopedPointer<Mlt::Producer> track_a(tractor.track(0));
1745         QScopedPointer<Mlt::Producer> track_b(tractor.track(1));
1746         int out = playlist.clip_length(clipIndex + 1) + delta - 1;
1747         playlist.block();
1748         track_a->set_in_and_out(track_a->get_in() - delta, track_a->get_out());
1749         track_b->set_in_and_out(track_b->get_in() - delta, track_b->get_out());
1750         playlist.unblock();
1751         tractor.multitrack()->set_in_and_out(0, out);
1752         tractor.set_in_and_out(0, out);
1753         producer->set("length", producer->frames_to_time(out + 1, mlt_time_clock));
1754         producer->set_in_and_out(0, out);
1755 
1756         // Adjust the transitions.
1757         QScopedPointer<Mlt::Service> service(tractor.producer());
1758         while (service && service->is_valid()) {
1759             if (service->type() == transition_type) {
1760                 Mlt::Transition transition(*service);
1761                 transition.set_in_and_out(0, out);
1762             }
1763             service.reset(service->producer());
1764         }
1765 
1766         // Adjust clip entry being trimmed.
1767         Mlt::ClipInfo info;
1768         playlist.clip_info(clipIndex, &info);
1769         playlist.resize_clip(clipIndex, info.frame_in, info.frame_out - delta);
1770 
1771         // Adjust filters.
1772         playlist.clip_info(clipIndex + 2, &info);
1773         adjustClipFilters(*info.producer, info.frame_in, info.frame_out, -(out + 1), 0);
1774 
1775         QVector<int> roles;
1776         roles << OutPointRole;
1777         roles << DurationRole;
1778         emit dataChanged(createIndex(clipIndex, 0, trackIndex),
1779                          createIndex(clipIndex + 1, 0, trackIndex), roles);
1780         emit modified();
1781     }
1782 }
1783 
trimTransitionOutValid(int trackIndex,int clipIndex,int delta)1784 bool MultitrackModel::trimTransitionOutValid(int trackIndex, int clipIndex, int delta)
1785 {
1786     if (m_isMakingTransition) return false;
1787     bool result = false;
1788     int i = m_trackList.at(trackIndex).mlt_index;
1789     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1790     if (track) {
1791         Mlt::Playlist playlist(*track);
1792         if (clipIndex > 1) {
1793             Mlt::ClipInfo info;
1794             // Check if there is already a transition.
1795             if (isTransition(playlist, clipIndex - 1)) {
1796                 // Check clip A out point.
1797                 playlist.clip_info(clipIndex - 2, &info);
1798                 info.frame_out += playlist.clip_length(clipIndex - 1) + delta;
1799                 if (info.frame_out > info.frame_in && info.frame_out < info.length) {
1800                     // Check clip B in point.
1801                     playlist.clip_info(clipIndex, &info);
1802                     info.frame_in += delta;
1803                     if (info.frame_in >= 0 && info.frame_in <= info.frame_out)
1804                         result = true;
1805                 }
1806             }
1807         }
1808     }
1809     return result;
1810 }
1811 
trimTransitionOut(int trackIndex,int clipIndex,int delta)1812 void MultitrackModel::trimTransitionOut(int trackIndex, int clipIndex, int delta)
1813 {
1814 //    LOG_DEBUG() << "clipIndex" << clipIndex << "delta" << delta;
1815     int i = m_trackList.at(trackIndex).mlt_index;
1816     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1817     if (track) {
1818         Mlt::Playlist playlist(*track);
1819 
1820         // Adjust the playlist "mix" entry.
1821         QScopedPointer<Mlt::Producer> producer(playlist.get_clip(clipIndex - 1));
1822         Mlt::Tractor tractor(producer->parent());
1823         if (!tractor.is_valid())
1824             return;
1825         QScopedPointer<Mlt::Producer> track_a(tractor.track(0));
1826         QScopedPointer<Mlt::Producer> track_b(tractor.track(1));
1827         int out = playlist.clip_length(clipIndex - 1) + delta - 1;
1828         playlist.block();
1829         track_a->set_in_and_out(track_a->get_in(), track_a->get_out() + delta);
1830         track_b->set_in_and_out(track_b->get_in(), track_b->get_out() + delta);
1831         playlist.unblock();
1832         tractor.multitrack()->set_in_and_out(0, out);
1833         tractor.set_in_and_out(0, out);
1834         producer->set("length", producer->frames_to_time(out + 1, mlt_time_clock));
1835         producer->set_in_and_out(0, out);
1836 
1837         // Adjust the transitions.
1838         QScopedPointer<Mlt::Service> service(tractor.producer());
1839         while (service && service->is_valid()) {
1840             if (service->type() == transition_type) {
1841                 Mlt::Transition transition(*service);
1842                 transition.set_in_and_out(0, out);
1843             }
1844             service.reset(service->producer());
1845         }
1846 
1847         // Adjust clip entry being trimmed.
1848         Mlt::ClipInfo info;
1849         playlist.clip_info(clipIndex, &info);
1850         playlist.resize_clip(clipIndex, info.frame_in + delta, info.frame_out);
1851 
1852         // Adjust filters.
1853         playlist.clip_info(clipIndex - 2, &info);
1854         adjustClipFilters(*info.producer, info.frame_in, info.frame_out, 0, -(out + 1));
1855 
1856         QVector<int> roles;
1857         roles << OutPointRole;
1858         roles << DurationRole;
1859         emit dataChanged(createIndex(clipIndex - 1, 0, trackIndex),
1860                          createIndex(clipIndex - 1, 0, trackIndex), roles);
1861         roles.clear();
1862         roles << InPointRole;
1863         roles << DurationRole;
1864         emit dataChanged(createIndex(clipIndex, 0, trackIndex),
1865                          createIndex(clipIndex, 0, trackIndex), roles);
1866         emit modified();
1867     }
1868 }
1869 
addTransitionByTrimInValid(int trackIndex,int clipIndex,int delta)1870 bool MultitrackModel::addTransitionByTrimInValid(int trackIndex, int clipIndex, int delta)
1871 {
1872     Q_UNUSED(delta)
1873     bool result = false;
1874     int i = m_trackList.at(trackIndex).mlt_index;
1875     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1876     if (track) {
1877         Mlt::Playlist playlist(*track);
1878         if (clipIndex > 0) {
1879             // Check if preceding clip is not blank, not already a transition,
1880             // and there is enough frames before in point of current clip.
1881             if (!m_isMakingTransition && delta < 0 && !playlist.is_blank(clipIndex - 1) && !isTransition(playlist, clipIndex - 1)) {
1882                 Mlt::ClipInfo info;
1883                 playlist.clip_info(clipIndex, &info);
1884                 if (info.frame_in >= -delta)
1885                     result = true;
1886             } else if (m_isMakingTransition && isTransition(playlist, clipIndex - 1)) {
1887                 // Invalid if transition length will be 0 or less.
1888                 auto newTransitionDuration = playlist.clip_length(clipIndex - 1) - delta;
1889                 result = newTransitionDuration > 0;
1890                 if (result && clipIndex > 1) {
1891                     QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(clipIndex));
1892                     // Invalid if left clip length will be 0 or less.
1893                     result = playlist.clip_length(clipIndex - 2) + delta > 0 &&
1894                              // Invalid if current clip in point will be less than 0.
1895                              info && info->frame_in - newTransitionDuration >= 0;
1896                 }
1897             } else {
1898                 result = m_isMakingTransition;
1899             }
1900         }
1901     }
1902     return result;
1903 }
1904 
addTransitionByTrimIn(int trackIndex,int clipIndex,int delta)1905 int MultitrackModel::addTransitionByTrimIn(int trackIndex, int clipIndex, int delta)
1906 {
1907     int i = m_trackList.at(trackIndex).mlt_index;
1908     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1909     if (track) {
1910         Mlt::Playlist playlist(*track);
1911 
1912         // Create transition if it does not yet exist.
1913         if (!isTransition(playlist, clipIndex - 1)) {
1914             // Adjust filters.
1915             Mlt::ClipInfo info;
1916             playlist.clip_info(clipIndex, &info);
1917             adjustClipFilters(*info.producer, info.frame_in, info.frame_out, delta, 0);
1918 
1919             // Insert the mix clip.
1920             beginInsertRows(index(trackIndex), clipIndex, clipIndex);
1921             playlist.mix_out(clipIndex - 1, -delta);
1922             QScopedPointer<Mlt::Producer> producer(playlist.get_clip(clipIndex));
1923             producer->parent().set(kShotcutTransitionProperty, kShotcutDefaultTransition);
1924             endInsertRows();
1925 
1926             // Add transitions.
1927             Mlt::Transition dissolve(MLT.profile(), Settings.playerGPU()? "movit.luma_mix" : "luma");
1928             Mlt::Transition crossFade(MLT.profile(), "mix:-1");
1929             if (!Settings.playerGPU()) dissolve.set("alpha_over", 1);
1930             playlist.mix_add(clipIndex, &dissolve);
1931             playlist.mix_add(clipIndex, &crossFade);
1932 
1933             // Notify clip A changed.
1934             QModelIndex modelIndex = createIndex(clipIndex - 1, 0, trackIndex);
1935             QVector<int> roles;
1936             roles << OutPointRole;
1937             roles << DurationRole;
1938             emit dataChanged(modelIndex, modelIndex, roles);
1939             emit modified();
1940             m_isMakingTransition = true;
1941             clipIndex += 1;
1942         } else if (m_isMakingTransition) {
1943             // Adjust a transition addition already in progress.
1944             // m_isMakingTransition will be set false when mouse button released via notifyClipOut().
1945             trimTransitionIn(trackIndex, clipIndex - 2, -delta);
1946         }
1947     }
1948     return clipIndex;
1949 }
1950 
addTransitionByTrimOutValid(int trackIndex,int clipIndex,int delta)1951 bool MultitrackModel::addTransitionByTrimOutValid(int trackIndex, int clipIndex, int delta)
1952 {
1953     Q_UNUSED(delta)
1954     bool result = false;
1955     int i = m_trackList.at(trackIndex).mlt_index;
1956     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1957     if (track) {
1958         Mlt::Playlist playlist(*track);
1959         if (clipIndex + 1 < playlist.count()) {
1960             // Check if following clip is not blank, not already a transition,
1961             // and there is enough frames after out point of current clip.
1962             if (!m_isMakingTransition && delta < 0 && !playlist.is_blank(clipIndex + 1) && !isTransition(playlist, clipIndex +  1)) {
1963                 Mlt::ClipInfo info;
1964                 playlist.clip_info(clipIndex, &info);
1965 //                LOG_DEBUG() << "(info.length" << info.length << " - info.frame_out" << info.frame_out << ") =" << (info.length - info.frame_out) << " >= -delta" << -delta;
1966                 if ((info.length - info.frame_out) >= -delta)
1967                     result = true;
1968             } else if (m_isMakingTransition && isTransition(playlist, clipIndex + 1)) {
1969                 // Invalid if transition length will be 0 or less.
1970                 auto newTransitionDuration = playlist.clip_length(clipIndex + 1) - delta;
1971 //                LOG_DEBUG() << "playlist.clip_length(clipIndex + 1)" << playlist.clip_length(clipIndex + 1) << "- delta" << delta << "=" << (playlist.clip_length(clipIndex + 1) - delta);
1972                 result = newTransitionDuration > 0;
1973                 if (result && clipIndex + 2 < playlist.count()) {
1974                     QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(clipIndex));
1975                     // Invalid if right clip length will be 0 or less.
1976                     result = playlist.clip_length(clipIndex + 2) + delta > 0 &&
1977                              // Invalid if current clip out point would exceed its duration.
1978                              info && info->frame_out + newTransitionDuration < info->length;
1979                 }
1980             } else {
1981                 result = m_isMakingTransition;
1982             }
1983         }
1984     }
1985     return result;
1986 }
1987 
addTransitionByTrimOut(int trackIndex,int clipIndex,int delta)1988 void MultitrackModel::addTransitionByTrimOut(int trackIndex, int clipIndex, int delta)
1989 {
1990     int i = m_trackList.at(trackIndex).mlt_index;
1991     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
1992     if (track) {
1993         Mlt::Playlist playlist(*track);
1994 
1995         // Create transition if it does not yet exist.
1996         if (!isTransition(playlist, clipIndex + 1)) {
1997             // Adjust filters.
1998             Mlt::ClipInfo info;
1999             playlist.clip_info(clipIndex, &info);
2000             adjustClipFilters(*info.producer, info.frame_in, info.frame_out, 0, delta);
2001 
2002             // Insert the mix clip.
2003             beginInsertRows(index(trackIndex), clipIndex + 1, clipIndex + 1);
2004             playlist.mix_in(clipIndex, -delta);
2005             QScopedPointer<Mlt::Producer> producer(playlist.get_clip(clipIndex + 1));
2006             producer->parent().set(kShotcutTransitionProperty, kShotcutDefaultTransition);
2007             endInsertRows();
2008 
2009             // Add transitions.
2010             Mlt::Transition dissolve(MLT.profile(), Settings.playerGPU()? "movit.luma_mix" : "luma");
2011             Mlt::Transition crossFade(MLT.profile(), "mix:-1");
2012             if (!Settings.playerGPU()) dissolve.set("alpha_over", 1);
2013             playlist.mix_add(clipIndex + 1, &dissolve);
2014             playlist.mix_add(clipIndex + 1, &crossFade);
2015 
2016             // Notify clip B changed.
2017             QModelIndex modelIndex = createIndex(clipIndex + 2, 0, trackIndex);
2018             QVector<int> roles;
2019             roles << InPointRole;
2020             roles << DurationRole;
2021             emit dataChanged(modelIndex, modelIndex, roles);
2022             emit modified();
2023             m_isMakingTransition = true;
2024         } else if (m_isMakingTransition) {
2025             // Adjust a transition addition already in progress.
2026             // m_isMakingTransition will be set false when mouse button released via notifyClipIn().
2027             delta = playlist.clip_start(clipIndex + 1) - (playlist.clip_start(clipIndex) + playlist.clip_length(clipIndex) + delta);
2028             trimTransitionOut(trackIndex, clipIndex + 2, delta);
2029         }
2030     }
2031 }
2032 
removeTransitionByTrimInValid(int trackIndex,int clipIndex,int delta)2033 bool MultitrackModel::removeTransitionByTrimInValid(int trackIndex, int clipIndex, int delta)
2034 {
2035     bool result = false;
2036     int i = m_trackList.at(trackIndex).mlt_index;
2037     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
2038     if (track) {
2039         Mlt::Playlist playlist(*track);
2040         if (clipIndex > 1) {
2041             // Check if there is a transition and its new length is 0 or less.
2042             if (isTransition(playlist, clipIndex - 1) && playlist.clip_length(clipIndex - 1) - qAbs(delta) <= 0) {
2043                 result = true;
2044                 m_isMakingTransition = false;
2045             }
2046         }
2047     }
2048     return result;
2049 }
2050 
removeTransitionByTrimOutValid(int trackIndex,int clipIndex,int delta)2051 bool MultitrackModel::removeTransitionByTrimOutValid(int trackIndex, int clipIndex, int delta)
2052 {
2053     bool result = false;
2054     int i = m_trackList.at(trackIndex).mlt_index;
2055     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
2056     if (track) {
2057         Mlt::Playlist playlist(*track);
2058         if (clipIndex + 2 < playlist.count()) {
2059             // Check if there is a transition and its new length is 0 or less.
2060 //            LOG_DEBUG() << "transition length" << playlist.clip_length(clipIndex + 1) << "delta" << delta << playlist.clip_length(clipIndex + 1) - qAbs(delta);
2061             if (isTransition(playlist, clipIndex + 1) && playlist.clip_length(clipIndex + 1) - qAbs(delta) <= 0) {
2062                 result = true;
2063                 m_isMakingTransition = false;
2064             }
2065         }
2066     }
2067     return result;
2068 }
2069 
filterAddedOrRemoved(Mlt::Producer * producer)2070 void MultitrackModel::filterAddedOrRemoved(Mlt::Producer* producer)
2071 {
2072     if (!m_tractor || !producer || !producer->is_valid())
2073         return;
2074     mlt_service service = producer->get_service();
2075 
2076     // Check if it was on the multitrack tractor.
2077     if (service == m_tractor->get_service())
2078         emit filteredChanged();
2079     else if (producer->get(kMultitrackItemProperty)) {
2080         // Check if it was a clip.
2081         QString s = QString::fromLatin1(producer->get(kMultitrackItemProperty));
2082         QVector<QStringRef> parts = s.splitRef(':');
2083         if (parts.length() == 2) {
2084             QModelIndex modelIndex = createIndex(parts[0].toInt(), 0, parts[1].toInt());
2085             QVector<int> roles;
2086             roles << FadeInRole;
2087             roles << FadeOutRole;
2088             emit dataChanged(modelIndex, modelIndex, roles);
2089         }
2090     } else for (int i = 0; i < m_trackList.size(); i++) {
2091         // Check if it was on one of the tracks.
2092         QScopedPointer<Mlt::Producer> track(m_tractor->track(m_trackList[i].mlt_index));
2093         if (service == track.data()->get_service()) {
2094             QModelIndex modelIndex = index(i, 0);
2095             QVector<int> roles;
2096             roles << IsFilteredRole;
2097             emit dataChanged(modelIndex, modelIndex, roles);
2098             break;
2099         }
2100     }
2101 }
2102 
onFilterChanged(Mlt::Filter * filter)2103 void MultitrackModel::onFilterChanged(Mlt::Filter* filter)
2104 {
2105     if (filter && filter->is_valid()) {
2106         Mlt::Service service(mlt_service(filter->get_data("service")));
2107         if (service.is_valid() && service.get(kMultitrackItemProperty)) {
2108             QString s = QString::fromLatin1(service.get(kMultitrackItemProperty));
2109             QVector<QStringRef> parts = s.splitRef(':');
2110             if (parts.length() == 2) {
2111                 QModelIndex modelIndex = createIndex(parts[0].toInt(), 0, parts[1].toInt());
2112                 QVector<int> roles;
2113                 const char* name = filter->get(kShotcutFilterProperty);
2114                 if (!qstrcmp("fadeInMovit", name) ||
2115                     !qstrcmp("fadeInBrightness", name) ||
2116                     !qstrcmp("fadeInVolume", name))
2117                     roles << FadeInRole;
2118                 if (!qstrcmp("fadeOutMovit", name) ||
2119                     !qstrcmp("fadeOutBrightness", name) ||
2120                     !qstrcmp("fadeOutVolume", name))
2121                     roles << FadeOutRole;
2122                 if (roles.length())
2123                     emit dataChanged(modelIndex, modelIndex, roles);
2124             }
2125         }
2126     }
2127 }
2128 
moveClipToEnd(Mlt::Playlist & playlist,int trackIndex,int clipIndex,int position,bool ripple,bool rippleAllTracks)2129 void MultitrackModel::moveClipToEnd(Mlt::Playlist& playlist, int trackIndex, int clipIndex, int position, bool ripple, bool rippleAllTracks)
2130 {
2131     int n = playlist.count();
2132     int length = position - playlist.clip_start(n - 1) - playlist.clip_length(n - 1);
2133     int clipPlaytime = playlist.clip_length(clipIndex);
2134     int clipStart = playlist.clip_start(clipIndex);
2135 
2136     if (!ripple) {
2137         if (clipIndex > 0 && playlist.is_blank(clipIndex - 1)) {
2138             // If there was a blank on the left adjust it.
2139             int duration = playlist.clip_length(clipIndex - 1) + playlist.clip_length(clipIndex);
2140 //            LOG_DEBUG() << "adjust blank on left to" << duration;
2141             playlist.resize_clip(clipIndex - 1, 0, duration - 1);
2142 
2143             QModelIndex index = createIndex(clipIndex - 1, 0, trackIndex);
2144             QVector<int> roles;
2145             roles << DurationRole;
2146             emit dataChanged(index, index, roles);
2147         } else if ((clipIndex + 1) < n && playlist.is_blank(clipIndex + 1)) {
2148             // If there was a blank on the right adjust it.
2149             int duration = playlist.clip_length(clipIndex + 1) + playlist.clip_length(clipIndex);
2150 //            LOG_DEBUG() << "adjust blank on right to" << duration;
2151             playlist.resize_clip(clipIndex + 1, 0, duration - 1);
2152 
2153             QModelIndex index = createIndex(clipIndex + 1, 0, trackIndex);
2154             QVector<int> roles;
2155             roles << DurationRole;
2156             emit dataChanged(index, index, roles);
2157         } else {
2158             // Add new blank
2159             beginInsertRows(index(trackIndex), clipIndex, clipIndex);
2160             playlist.insert_blank(clipIndex, playlist.clip_length(clipIndex) - 1);
2161             endInsertRows();
2162             ++clipIndex;
2163             ++n;
2164         }
2165     }
2166     // Add blank to end if needed.
2167     if (length > 0) {
2168         beginInsertRows(index(trackIndex), n, n);
2169         playlist.blank(length - 1);
2170         endInsertRows();
2171     }
2172     // Finally, move clip into place.
2173     QModelIndex parentIndex = index(trackIndex);
2174     if (playlist.count() < clipIndex || playlist.count() > clipIndex + 1) {
2175         beginMoveRows(parentIndex, clipIndex, clipIndex, parentIndex, playlist.count());
2176         playlist.move(clipIndex, playlist.count());
2177         endMoveRows();
2178         consolidateBlanks(playlist, trackIndex);
2179     }
2180 
2181     // Ripple all unlocked tracks.
2182     if (clipPlaytime > 0 && ripple && rippleAllTracks)
2183     for (int j = 0; j < m_trackList.count(); ++j) {
2184         if (j == trackIndex)
2185             continue;
2186 
2187         int mltIndex = m_trackList.at(j).mlt_index;
2188         QScopedPointer<Mlt::Producer> otherTrack(m_tractor->track(mltIndex));
2189         if (otherTrack) {
2190             if (otherTrack->get_int(kTrackLockProperty))
2191                 continue;
2192 
2193             removeRegion(j, clipStart, clipPlaytime);
2194         }
2195     }
2196 }
2197 
moveClipInBlank(Mlt::Playlist & playlist,int trackIndex,int clipIndex,int position,bool ripple,bool rippleAllTracks,int duration)2198 void MultitrackModel::moveClipInBlank(Mlt::Playlist& playlist, int trackIndex, int clipIndex, int position, bool ripple, bool rippleAllTracks, int duration)
2199 {
2200     int clipPlaytime = duration? duration : playlist.clip_length(clipIndex);
2201     int clipStart = playlist.clip_start(clipIndex);
2202     int delta = position - clipStart;
2203 
2204     if (clipIndex > 0 && playlist.is_blank(clipIndex - 1)) {
2205         // Adjust blank on left.
2206         int duration = playlist.clip_length(clipIndex - 1) + delta;
2207         if (duration > 0) {
2208 //            LOG_DEBUG() << "adjust blank on left" << (clipIndex - 1) << "to" << duration;
2209             playlist.resize_clip(clipIndex - 1, 0, duration - 1);
2210 
2211             QModelIndex index = createIndex(clipIndex - 1, 0, trackIndex);
2212             QVector<int> roles;
2213             roles << DurationRole;
2214             emit dataChanged(index, index, roles);
2215         } else {
2216 //            LOG_DEBUG() << "remove blank on left";
2217             int i = clipIndex - 1;
2218             beginRemoveRows(index(trackIndex), i, i);
2219             playlist.remove(i);
2220             endRemoveRows();
2221             consolidateBlanks(playlist, trackIndex);
2222             --clipIndex;
2223         }
2224     } else if (delta > 0) {
2225 //        LOG_DEBUG() << "add blank on left with duration" << delta;
2226         // Add blank to left.
2227         int i = qMax(clipIndex, 0);
2228         beginInsertRows(index(trackIndex), i, i);
2229         playlist.insert_blank(i, delta - 1);
2230         endInsertRows();
2231         ++clipIndex;
2232     }
2233 
2234     if (!ripple && (clipIndex + 1) < playlist.count() && playlist.is_blank(clipIndex + 1)) {
2235         // Adjust blank to right.
2236         int duration = playlist.clip_length(clipIndex + 1) - delta;
2237         if (duration > 0) {
2238 //            LOG_DEBUG() << "adjust blank on right" << (clipIndex + 1) << "to" << duration;
2239             playlist.resize_clip(clipIndex + 1, 0, duration - 1);
2240 
2241             QModelIndex index = createIndex(clipIndex + 1, 0, trackIndex);
2242             QVector<int> roles;
2243             roles << DurationRole;
2244             emit dataChanged(index, index, roles);
2245         } else {
2246 //            LOG_DEBUG() << "remove blank on right";
2247             int i = clipIndex + 1;
2248             beginRemoveRows(index(trackIndex), i, i);
2249             playlist.remove(i);
2250             endRemoveRows();
2251             consolidateBlanks(playlist, trackIndex);
2252         }
2253     } else if (!ripple && delta < 0 && (clipIndex + 1) < playlist.count()) {
2254         // Add blank to right.
2255 //        LOG_DEBUG() << "add blank on right with duration" << -delta;
2256         beginInsertRows(index(trackIndex), clipIndex + 1, clipIndex + 1);
2257         playlist.insert_blank(clipIndex + 1, (-delta - 1));
2258         endInsertRows();
2259     }
2260 
2261     // Ripple all unlocked tracks.
2262     if (clipPlaytime > 0 && ripple && rippleAllTracks) {
2263         QList<int> otherTracksToRipple;
2264         for (int i = 0; i < m_trackList.count(); ++i) {
2265             if (i == trackIndex)
2266                 continue;
2267             int mltIndex = m_trackList.at(i).mlt_index;
2268             QScopedPointer<Mlt::Producer> otherTrack(m_tractor->track(mltIndex));
2269             if (otherTrack && otherTrack->get_int(kTrackLockProperty))
2270                 continue;
2271             otherTracksToRipple << i;
2272         }
2273         if (position < clipStart) {
2274             foreach (int idx, otherTracksToRipple)
2275                 removeRegion(idx, position, clipStart - position);
2276         } else {
2277             insertOrAdjustBlankAt(otherTracksToRipple, clipStart, position - clipStart);
2278             consolidateBlanks(playlist, trackIndex);
2279         }
2280     }
2281 }
2282 
consolidateBlanks(Mlt::Playlist & playlist,int trackIndex)2283 void MultitrackModel::consolidateBlanks(Mlt::Playlist &playlist, int trackIndex)
2284 {
2285     for (int i = 1; i < playlist.count(); i++) {
2286         if (playlist.is_blank(i - 1) && playlist.is_blank(i)) {
2287             int out = playlist.clip_length(i - 1) + playlist.clip_length(i) - 1;
2288             playlist.resize_clip(i - 1, 0, out);
2289             QModelIndex idx = createIndex(i - 1, 0, trackIndex);
2290             QVector<int> roles;
2291             roles << DurationRole;
2292             emit dataChanged(idx, idx, roles);
2293             beginRemoveRows(index(trackIndex), i, i);
2294             playlist.remove(i--);
2295             endRemoveRows();
2296         }
2297     }
2298     if (playlist.count() > 0) {
2299         int i = playlist.count() - 1;
2300         if (playlist.is_blank(i)) {
2301             beginRemoveRows(index(trackIndex), i, i);
2302             playlist.remove(i);
2303             endRemoveRows();
2304         }
2305     }
2306     if (playlist.count() == 0) {
2307         beginInsertRows(index(trackIndex), 0, 0);
2308         playlist.blank(0);
2309         endInsertRows();
2310     }
2311 }
2312 
consolidateBlanksAllTracks()2313 void MultitrackModel::consolidateBlanksAllTracks()
2314 {
2315     if (!m_tractor) return;
2316     int i = 0;
2317     foreach (Track t, m_trackList) {
2318         Mlt::Producer* track = m_tractor->track(t.mlt_index);
2319         if (track) {
2320             Mlt::Playlist playlist(*track);
2321             consolidateBlanks(playlist, i);
2322         }
2323         ++i;
2324     }
2325 }
2326 
audioLevelsReady(const QModelIndex & index)2327 void MultitrackModel::audioLevelsReady(const QModelIndex& index)
2328 {
2329     QVector<int> roles;
2330     roles << AudioLevelsRole;
2331     emit dataChanged(index, index, roles);
2332 }
2333 
createIfNeeded()2334 bool MultitrackModel::createIfNeeded()
2335 {
2336     if (!m_tractor) {
2337         m_tractor = new Mlt::Tractor(MLT.profile());
2338         MLT.profile().set_explicit(true);
2339         m_tractor->set(kShotcutXmlProperty, 1);
2340         retainPlaylist();
2341         addBackgroundTrack();
2342         addVideoTrack();
2343         emit created();
2344     } else if (!m_trackList.count()) {
2345         addVideoTrack();
2346     }
2347     return true;
2348 }
2349 
addBackgroundTrack()2350 void MultitrackModel::addBackgroundTrack()
2351 {
2352     Mlt::Playlist playlist(MLT.profile());
2353     playlist.set("id", kBackgroundTrackId);
2354     Mlt::Producer producer(MLT.profile(), "color:0");
2355     producer.set("mlt_image_format", "rgb24a");
2356     producer.set("length", 1);
2357     producer.set("id", "black");
2358     // Allow mixing against frames produced by this producer.
2359     producer.set("set.test_audio", 0);
2360     playlist.append(producer);
2361     m_tractor->set_track(playlist, m_tractor->count());
2362 }
2363 
adjustBackgroundDuration()2364 void MultitrackModel::adjustBackgroundDuration()
2365 {
2366     if (!m_tractor) return;
2367     int duration = getDuration();
2368     Mlt::Producer* track = m_tractor->track(0);
2369     if (track) {
2370         Mlt::Playlist playlist(*track);
2371         Mlt::Producer* clip = playlist.get_clip(0);
2372         if (clip) {
2373             if (duration != clip->parent().get_length()) {
2374                 clip->parent().set("length", clip->parent().frames_to_time(duration, mlt_time_clock));
2375                 clip->parent().set_in_and_out(0, duration - 1);
2376                 clip->set("length", clip->parent().frames_to_time(duration, mlt_time_clock));
2377                 clip->set_in_and_out(0, duration - 1);
2378                 playlist.resize_clip(0, 0, duration - 1);
2379                 emit durationChanged();
2380             }
2381             delete clip;
2382         }
2383         delete track;
2384     }
2385 }
2386 
adjustServiceFilterDurations(Mlt::Service & service,int duration)2387 void MultitrackModel::adjustServiceFilterDurations(Mlt::Service& service, int duration)
2388 {
2389     // Use kFilterOutProperty to track duration changes
2390     if (service.get(kFilterOutProperty)) {
2391         int oldOut = service.get_int(kFilterOutProperty);
2392         int n = service.filter_count();
2393         for (int i = 0; i < n; i++) {
2394             QScopedPointer<Mlt::Filter> filter(service.filter(i));
2395             if (filter && filter->is_valid() && !filter->get_int("_loader")) {
2396                 int in = filter->get_in();
2397                 int out = filter->get_out();
2398                 // Only change out if it is pinned (same as old track duration)
2399                 if (out == oldOut || in < 0) {
2400                     out = duration - 1;
2401                     filter->set_in_and_out(qMax(in, 0), out);
2402                 }
2403             }
2404         }
2405     }
2406     service.set(kFilterOutProperty, duration - 1);
2407 }
2408 
warnIfInvalid(Mlt::Service & service)2409 bool MultitrackModel::warnIfInvalid(Mlt::Service& service)
2410 {
2411     if (!service.is_valid()) {
2412         const char* plugin = Settings.playerGPU()? "Movit overlay" : "frei0r cairoblend";
2413         const char* plugins = Settings.playerGPU()? "Movit" : "frei0r";
2414         LongUiTask::cancel();
2415         QMessageBox::critical(&MAIN, qApp->applicationName(),
2416             tr("Error: Shotcut could not find the %1 plugin on your system.\n\n"
2417                "Please install the %2 plugins.").arg(plugin).arg(plugins));
2418         return true;
2419     }
2420     return false;
2421 }
2422 
adjustTrackFilters()2423 void MultitrackModel::adjustTrackFilters()
2424 {
2425     if (!m_tractor) return;
2426     int duration = getDuration();
2427 
2428     // Adjust filters on the tractor.
2429     adjustServiceFilterDurations(*m_tractor, duration);
2430 
2431     // Adjust filters on the tracks.
2432     foreach (Track t, m_trackList) {
2433         QScopedPointer<Mlt::Producer> track(m_tractor->track(t.mlt_index));
2434         if (track && track->is_valid())
2435             adjustServiceFilterDurations(*track, duration);
2436     }
2437 }
2438 
adjustClipFilters(Mlt::Producer & producer,int in,int out,int inDelta,int outDelta)2439 void MultitrackModel::adjustClipFilters(Mlt::Producer& producer, int in, int out, int inDelta, int outDelta)
2440 {
2441     for (int j = 0; j < producer.filter_count(); j++) {
2442         QScopedPointer<Mlt::Filter> filter(producer.filter(j));
2443         if (filter && filter->is_valid()) {
2444             QString filterName = filter->get(kShotcutFilterProperty);
2445 
2446             if (inDelta) {
2447                 if (filterName.startsWith("fadeIn")) {
2448                     if (!filter->get(kShotcutAnimInProperty)) {
2449                         // Convert legacy fadeIn filters.
2450                         filter->set(kShotcutAnimInProperty, filter->get_length());
2451                     }
2452                     filter->set_in_and_out(in + inDelta, filter->get_out());
2453                     emit filterInChanged(inDelta, filter.data());
2454                 } else if (!filter->get_int("_loader") && filter->get_in() <= in) {
2455                     filter->set_in_and_out(in + inDelta, filter->get_out());
2456                     emit filterInChanged(inDelta, filter.data());
2457                 }
2458             }
2459 
2460             if (filterName.startsWith("fadeOut")) {
2461                 if (!filter->get(kShotcutAnimOutProperty)) {
2462                     // Convert legacy fadeOut filters.
2463                     filter->set(kShotcutAnimOutProperty, filter->get_length());
2464                 }
2465                 filter->set_in_and_out(filter->get_in(), out - outDelta);
2466                 if (filterName == "fadeOutBrightness") {
2467                     const char* key = filter->get_int("alpha") != 1? "alpha" : "level";
2468                     filter->clear(key);
2469                     filter->anim_set(key, 1, filter->get_length() - filter->get_int(kShotcutAnimOutProperty));
2470                     filter->anim_set(key, 0, filter->get_length() - 1);
2471                 } else if (filterName == "fadeOutMovit") {
2472                     filter->clear("opacity");
2473                     filter->anim_set("opacity", 1, filter->get_length() - filter->get_int(kShotcutAnimOutProperty), 0, mlt_keyframe_smooth);
2474                     filter->anim_set("opacity", 0, filter->get_length() - 1);
2475                 } else if (filterName == "fadeOutVolume") {
2476                     filter->clear("level");
2477                     filter->anim_set("level", 0, filter->get_length() - filter->get_int(kShotcutAnimOutProperty));
2478                     filter->anim_set("level", -60, filter->get_length() - 1);
2479                 }
2480                 emit filterOutChanged(outDelta, filter.data());
2481             } else if (!filter->get_int("_loader") && filter->get_out() >= out) {
2482                 filter->set_in_and_out(filter->get_in(), out - outDelta);
2483                 emit filterOutChanged(outDelta, filter.data());
2484 
2485                 // Update simple keyframes of non-current filters.
2486                 if (filter->get_int(kShotcutAnimOutProperty) > 0
2487                     && MAIN.filterController()->currentFilter()
2488                     && MAIN.filterController()->currentFilter()->filter().get_filter() != filter.data()->get_filter()) {
2489                     QmlMetadata* meta = MAIN.filterController()->metadataForService(filter.data());
2490                     if (meta && meta->keyframes()) {
2491                         foreach (QString name, meta->keyframes()->simpleProperties()) {
2492                             if (!filter->get_animation(name.toUtf8().constData()))
2493                                 // Cause a string property to be interpreted as animated value.
2494                                 filter->anim_get_double(name.toUtf8().constData(), 0, filter->get_length());
2495                             Mlt::Animation animation = filter->get_animation(name.toUtf8().constData());
2496                             if (animation.is_valid()) {
2497                                 int n = animation.key_count();
2498                                 if (n > 1) {
2499                                     animation.set_length(filter->get_length());
2500                                     animation.key_set_frame(n - 2, filter->get_length() - filter->get_int(kShotcutAnimOutProperty));
2501                                     animation.key_set_frame(n - 1, filter->get_length() - 1);
2502                                 }
2503                             }
2504                         }
2505                     }
2506                 }
2507             }
2508         }
2509     }
2510 }
2511 
findClipByUuid(const QUuid & uuid,int & trackIndex,int & clipIndex)2512 Mlt::ClipInfo* MultitrackModel::findClipByUuid(const QUuid &uuid, int &trackIndex, int &clipIndex)
2513 {
2514     for (trackIndex = 0; trackIndex < trackList().size(); trackIndex++) {
2515         int i = trackList().at(trackIndex).mlt_index;
2516         QScopedPointer<Mlt::Producer> track(tractor()->track(i));
2517         if (track) {
2518             Mlt::Playlist playlist(*track);
2519             for (clipIndex = 0; clipIndex < playlist.count(); clipIndex++) {
2520                 Mlt::ClipInfo* info;
2521                 if ((info = playlist.clip_info(clipIndex))) {
2522                     if (MLT.uuid(*info->cut) == uuid)
2523                         return info;
2524                 }
2525             }
2526         }
2527     }
2528     return nullptr;
2529 }
2530 
addAudioTrack()2531 int MultitrackModel::addAudioTrack()
2532 {
2533     if (!m_tractor) {
2534         m_tractor = new Mlt::Tractor(MLT.profile());
2535         MLT.profile().set_explicit(true);
2536         m_tractor->set(kShotcutXmlProperty, 1);
2537         retainPlaylist();
2538         addBackgroundTrack();
2539         addAudioTrack();
2540         emit created();
2541         emit modified();
2542         return 0;
2543     }
2544 
2545     // Get the new track index.
2546     int i = m_tractor->count();
2547 
2548     // Create the MLT track.
2549     Mlt::Playlist playlist(MLT.profile());
2550     playlist.set(kAudioTrackProperty, 1);
2551     playlist.set("hide", 1);
2552     playlist.blank(0);
2553     m_tractor->set_track(playlist, i);
2554     MLT.updateAvformatCaching(m_tractor->count());
2555 
2556     // Add the mix transition.
2557     Mlt::Transition mix(MLT.profile(), "mix");
2558     mix.set("always_active", 1);
2559     mix.set("sum", 1);
2560     m_tractor->plant_transition(mix, 0, i);
2561 
2562     // Get the new, logical audio-only index.
2563     int a = 0;
2564     foreach (Track t, m_trackList) {
2565         if (t.type == AudioTrackType)
2566             ++a;
2567     }
2568 
2569     // Add the shotcut logical audio track.
2570     Track t;
2571     t.mlt_index = i;
2572     t.type = AudioTrackType;
2573     t.number = a++;
2574     QString trackName = QString("A%1").arg(a);
2575     playlist.set(kTrackNameProperty, trackName.toUtf8().constData());
2576     beginInsertRows(QModelIndex(), m_trackList.count(), m_trackList.count());
2577     m_trackList.append(t);
2578     endInsertRows();
2579     emit modified();
2580     return m_trackList.count() - 1;
2581 }
2582 
addVideoTrack()2583 int MultitrackModel::addVideoTrack()
2584 {
2585     if (!m_tractor) {
2586         createIfNeeded();
2587         return 0;
2588     }
2589 
2590     // Get the new track index.
2591     int i = m_tractor->count();
2592 
2593     // Create the MLT track.
2594     Mlt::Playlist playlist(MLT.profile());
2595     playlist.set(kVideoTrackProperty, 1);
2596     playlist.blank(0);
2597     m_tractor->set_track(playlist, i);
2598     MLT.updateAvformatCaching(m_tractor->count());
2599 
2600     // Add the mix transition.
2601     Mlt::Transition mix(MLT.profile(), "mix");
2602     mix.set("always_active", 1);
2603     mix.set("sum", 1);
2604     m_tractor->plant_transition(mix, 0, i);
2605 
2606     // Add the composite transition.
2607     Mlt::Transition composite(MLT.profile(), Settings.playerGPU()? "movit.overlay" : "frei0r.cairoblend");
2608     if (!composite.is_valid() && !Settings.playerGPU()) {
2609         composite = Mlt::Transition(MLT.profile(), "qtblend");
2610     } else if (composite.is_valid() && !Settings.playerGPU()) {
2611         composite.set("threads", 0);
2612     }
2613     if (warnIfInvalid(composite)) {
2614         return -1;
2615     }
2616     composite.set("disable", 1);
2617     foreach (Track t, m_trackList) {
2618         if (t.type == VideoTrackType) {
2619             composite.set("disable", 0);
2620             break;
2621         }
2622     }
2623 
2624     // Get the new, logical video-only index.
2625     int v = 0;
2626     int last_mlt_index = 0;
2627     foreach (Track t, m_trackList) {
2628         if (t.type == VideoTrackType) {
2629             ++v;
2630             last_mlt_index = t.mlt_index;
2631         }
2632     }
2633     m_tractor->plant_transition(composite, last_mlt_index, i);
2634 
2635     // Add the shotcut logical video track.
2636     Track t;
2637     t.mlt_index = i;
2638     t.type = VideoTrackType;
2639     t.number = v++;
2640     QString trackName = QString("V%1").arg(v);
2641     playlist.set(kTrackNameProperty, trackName.toUtf8().constData());
2642     beginInsertRows(QModelIndex(), 0, 0);
2643     m_trackList.prepend(t);
2644     endInsertRows();
2645     emit modified();
2646     return 0;
2647 }
2648 
removeTrack(int trackIndex)2649 void MultitrackModel::removeTrack(int trackIndex)
2650 {
2651     if (trackIndex >= 0 && trackIndex < m_trackList.size()) {
2652         const Track& track = m_trackList.value(trackIndex);
2653         QScopedPointer<Mlt::Transition> transition(getTransition("frei0r.cairoblend", track.mlt_index));
2654 
2655         // Remove transitions.
2656         if (!transition)
2657             transition.reset(getTransition("qtblend", track.mlt_index));
2658         if (!transition)
2659             transition.reset(getTransition("movit.overlay", track.mlt_index));
2660         if (transition)
2661             m_tractor->field()->disconnect_service(*transition);
2662         transition.reset(getTransition("mix", track.mlt_index));
2663         if (transition)
2664             m_tractor->field()->disconnect_service(*transition);
2665 
2666 //        foreach (Track t, m_trackList) LOG_DEBUG() << (t.type == VideoTrackType?"Video":"Audio") << "track number" << t.number << "mlt_index" << t.mlt_index;
2667 //        LOG_DEBUG() << trackIndex << "mlt_index" << track.mlt_index;
2668 
2669         // Remove track.
2670         beginRemoveRows(QModelIndex(), trackIndex, trackIndex);
2671         m_tractor->remove_track(track.mlt_index);
2672         m_trackList.removeAt(trackIndex);
2673         endRemoveRows();
2674         MLT.updateAvformatCaching(m_tractor->count());
2675 
2676 //        foreach (Track t, m_trackList) LOG_DEBUG() << (t.type == VideoTrackType?"Video":"Audio") << "track number" << t.number << "mlt_index" << t.mlt_index;
2677 
2678         // Renumber other tracks.
2679         int row = 0;
2680         foreach (Track t, m_trackList) {
2681             if (t.mlt_index > track.mlt_index)
2682                 --m_trackList[row].mlt_index;
2683             if (t.type == track.type && t.number > track.number) {
2684                 --m_trackList[row].number;
2685                 QModelIndex modelIndex = index(row, 0);
2686 
2687                 // Disable compositing on the bottom video track.
2688                 if (m_trackList[row].number == 0 && t.type == VideoTrackType) {
2689                     QScopedPointer<Mlt::Transition> transition(getTransition("frei0r.cairoblend", 1));
2690                     if (!transition)
2691                         transition.reset(getTransition("qtblend", 1));
2692                     if (!transition)
2693                         transition.reset(getTransition("movit.overlay", 1));
2694                     if (transition && transition->is_valid())
2695                         transition->set("disable", 1);
2696                     emit dataChanged(modelIndex, modelIndex, QVector<int>() << IsBottomVideoRole << IsCompositeRole);
2697                 }
2698 
2699                 // Rename default track names.
2700                 QScopedPointer<Mlt::Producer> mltTrack(m_tractor->track(m_trackList[row].mlt_index));
2701                 QString trackNameTemplate = (t.type == VideoTrackType)? QString("V%1") : QString("A%1");
2702                 QString trackName = trackNameTemplate.arg(t.number + 1);
2703                 if (mltTrack && mltTrack->get(kTrackNameProperty) == trackName) {
2704                     trackName = trackNameTemplate.arg(m_trackList[row].number + 1);
2705                     mltTrack->set(kTrackNameProperty, trackName.toUtf8().constData());
2706                     emit dataChanged(modelIndex, modelIndex, QVector<int>() << NameRole);
2707                 }
2708             }
2709             ++row;
2710         }
2711 //        foreach (Track t, m_trackList) LOG_DEBUG() << (t.type == VideoTrackType?"Video":"Audio") << "track number" << t.number << "mlt_index" << t.mlt_index;
2712     }
2713     emit modified();
2714 }
2715 
retainPlaylist()2716 void MultitrackModel::retainPlaylist()
2717 {
2718     if (!MAIN.playlist())
2719         MAIN.playlistDock()->model()->createIfNeeded();
2720     Mlt::Playlist playlist(*MAIN.playlist());
2721     playlist.set("id", kPlaylistTrackId);
2722     QString retain = QString("xml_retain %1").arg(kPlaylistTrackId);
2723     m_tractor->set(retain.toUtf8().constData(), playlist.get_service(), 0);
2724 }
2725 
loadPlaylist()2726 void MultitrackModel::loadPlaylist()
2727 {
2728     Mlt::Properties retainList((mlt_properties) m_tractor->get_data("xml_retain"));
2729     if (retainList.is_valid()) {
2730         Mlt::Playlist playlist((mlt_playlist) retainList.get_data(kPlaylistTrackId));
2731         if (playlist.is_valid() && playlist.type() == playlist_type) {
2732             MAIN.playlistDock()->model()->setPlaylist(playlist);
2733         } else {
2734             playlist = (mlt_playlist) retainList.get_data(kLegacyPlaylistTrackId);
2735             if (playlist.is_valid() && playlist.type() == playlist_type)
2736                 MAIN.playlistDock()->model()->setPlaylist(playlist);
2737         }
2738     }
2739     retainPlaylist();
2740 }
2741 
removeRegion(int trackIndex,int position,int length)2742 void MultitrackModel::removeRegion(int trackIndex, int position, int length)
2743 {
2744     int i = m_trackList.at(trackIndex).mlt_index;
2745     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
2746     if (track) {
2747         Mlt::Playlist playlist(*track);
2748         int clipIndex = playlist.get_clip_index_at(position);
2749 
2750         if (clipIndex >= 0 && clipIndex < playlist.count())
2751         {
2752             int clipStart = playlist.clip_start(clipIndex);
2753             int playtime = playlist.get_playtime();
2754             playlist.block(playlist.get_playlist());
2755 
2756             if (position + length > playtime)
2757                 length -= (position + length - playtime);
2758 
2759             if (clipStart < position) {
2760                 splitClip(trackIndex, clipIndex, position);
2761                 ++clipIndex;
2762             }
2763 
2764             while (length > 0) {
2765                 if (playlist.clip_length(clipIndex) > length) {
2766                     splitClip(trackIndex, clipIndex, position + length);
2767                 }
2768                 length -= playlist.clip_length(clipIndex);
2769                 if (clipIndex < playlist.count()) {
2770                     // Shotcut does not like the behavior of remove() on a
2771                     // transition (MLT mix clip). So, we null mlt_mix to prevent it.
2772                     clearMixReferences(trackIndex, clipIndex);
2773                     beginRemoveRows(index(trackIndex), clipIndex, clipIndex);
2774                     playlist.remove(clipIndex);
2775                     endRemoveRows();
2776                 }
2777             }
2778             playlist.unblock(playlist.get_playlist());
2779             consolidateBlanks(playlist, trackIndex);
2780         }
2781     }
2782 }
2783 
isTransition(Mlt::Playlist & playlist,int clipIndex) const2784 bool MultitrackModel::isTransition(Mlt::Playlist &playlist, int clipIndex) const
2785 {
2786     QScopedPointer<Mlt::Producer> producer(playlist.get_clip(clipIndex));
2787     if (producer && producer->parent().get(kShotcutTransitionProperty))
2788         return true;
2789     return false;
2790 }
2791 
insertTrack(int trackIndex,TrackType type)2792 void MultitrackModel::insertTrack(int trackIndex, TrackType type)
2793 {
2794     if (!m_tractor || trackIndex <= 0) {
2795         addVideoTrack();
2796         return;
2797     }
2798 
2799     // Get the new track index.
2800     Track& track = m_trackList[qBound(0, trackIndex, m_trackList.count() - 1)];
2801     int i = track.mlt_index;
2802     QScopedPointer<Mlt::Transition> lower;
2803     const char* videoTransitionName = Settings.playerGPU()? "movit.overlay" : "frei0r.cairoblend";
2804     if (type == VideoTrackType) {
2805         ++i;
2806         auto transition = getTransition(videoTransitionName, track.mlt_index);
2807         if (!transition) {
2808             transition = getTransition("qtblend", track.mlt_index);
2809         }
2810         lower.reset(transition);
2811     }
2812 
2813     if (trackIndex >= m_trackList.count()) {
2814         if (type == AudioTrackType) {
2815             addAudioTrack();
2816             return;
2817         } else if (type == VideoTrackType) {
2818             i = track.mlt_index;
2819         }
2820     }
2821 
2822 //    foreach (Track t, m_trackList) LOG_DEBUG() << (t.type == VideoTrackType?"Video":"Audio") << "track number" << t.number << "mlt_index" << t.mlt_index;
2823 //    LOG_DEBUG() << "trackIndex" << trackIndex << "mlt_index" << i;
2824 
2825     // Get the new, logical video-only index.
2826     int videoTrackCount = 0;
2827     int last_mlt_index = 0;
2828     int row = 0;
2829     foreach (Track t, m_trackList) {
2830         if (t.type == track.type) {
2831             if ((t.type == VideoTrackType && t.number > track.number) ||
2832                 (t.type == AudioTrackType && t.number >= track.number)) {
2833                 // Rename default track names.
2834                 QScopedPointer<Mlt::Producer> mltTrack(m_tractor->track(t.mlt_index));
2835                 QString trackNameTemplate = (t.type == VideoTrackType)? QString("V%1") : QString("A%1");
2836                 QString trackName = trackNameTemplate.arg(++t.number);
2837                 if (mltTrack && mltTrack->get(kTrackNameProperty) == trackName) {
2838                     trackName = trackNameTemplate.arg(t.number + 1);
2839                     mltTrack->set(kTrackNameProperty, trackName.toUtf8().constData());
2840                     QModelIndex modelIndex = index(row, 0);
2841                     QVector<int> roles;
2842                     roles << NameRole;
2843                     emit dataChanged(modelIndex, modelIndex, roles);
2844                 }
2845                 ++m_trackList[row].number;
2846             }
2847         }
2848         if (t.mlt_index >= i)
2849             ++m_trackList[row].mlt_index;
2850         if (t.type == VideoTrackType) {
2851             ++videoTrackCount;
2852             last_mlt_index = t.mlt_index;
2853         }
2854         ++row;
2855     }
2856 
2857 //    foreach (Track t, m_trackList) LOG_DEBUG() << (t.type == VideoTrackType?"Video":"Audio") << "track number" << t.number << "mlt_index" << t.mlt_index;
2858 
2859     // Create the MLT track.
2860     Mlt::Playlist playlist(MLT.profile());
2861     if (type == VideoTrackType) {
2862         playlist.set(kVideoTrackProperty, 1);
2863     } else if (type == AudioTrackType) {
2864         playlist.set(kAudioTrackProperty, 1);
2865         playlist.set("hide", 1);
2866     }
2867     playlist.blank(0);
2868     m_tractor->insert_track(playlist, i);
2869     MLT.updateAvformatCaching(m_tractor->count());
2870 
2871     // Add the mix transition.
2872     Mlt::Transition mix(MLT.profile(), "mix");
2873     mix.set("always_active", 1);
2874     mix.set("sum", 1);
2875     m_tractor->plant_transition(mix, 0, i);
2876 
2877     if (type == VideoTrackType) {
2878         // Add the composite transition.
2879         Mlt::Transition composite(MLT.profile(), videoTransitionName);
2880         if (!composite.is_valid() && !Settings.playerGPU()) {
2881             composite = Mlt::Transition(MLT.profile(), "qtblend");
2882         } else if (composite.is_valid() && !Settings.playerGPU()) {
2883             composite.set("threads", 0);
2884         }
2885         if (warnIfInvalid(composite)) {
2886             return;
2887         }
2888         if (lower) {
2889             QScopedPointer<Mlt::Service> consumer(lower->consumer());
2890             if (consumer->is_valid()) {
2891                 // Insert the new transition.
2892                 LOG_DEBUG() << "inserting transition" << last_mlt_index << i;
2893                 composite.connect(*lower, last_mlt_index, i);
2894                 Mlt::Transition t((mlt_transition) consumer->get_service());
2895                 t.connect(composite, consumer->get_int("a_track"), consumer->get_int("b_track"));
2896             } else {
2897                 // Append the new transition.
2898                 LOG_DEBUG() << "appending transition";
2899                 m_tractor->plant_transition(composite, last_mlt_index, i);
2900             }
2901         } else {
2902             // Append the new transition.
2903             LOG_DEBUG() << "appending transition";
2904             m_tractor->plant_transition(composite, last_mlt_index, i);
2905         }
2906     }
2907 
2908     // Add the shotcut logical video track.
2909     Track t;
2910     t.mlt_index = i;
2911     t.type = type;
2912     QString trackName;
2913     if (t.type == VideoTrackType) {
2914         t.number = videoTrackCount - trackIndex;
2915         trackName = QString("V%1");
2916     } else if (t.type == AudioTrackType) {
2917         t.number = trackIndex - videoTrackCount;
2918         trackName = QString("A%1");
2919     }
2920     trackName = trackName.arg(t.number + 1);
2921     playlist.set(kTrackNameProperty, trackName.toUtf8().constData());
2922     beginInsertRows(QModelIndex(), trackIndex, trackIndex);
2923     m_trackList.insert(trackIndex, t);
2924     endInsertRows();
2925     emit modified();
2926 //    foreach (Track t, m_trackList) LOG_DEBUG() << (t.type == VideoTrackType?"Video":"Audio") << "track number" << t.number << "mlt_index" << t.mlt_index;
2927 }
2928 
insertOrAdjustBlankAt(QList<int> tracks,int position,int length)2929 void MultitrackModel::insertOrAdjustBlankAt(QList<int> tracks, int position, int length)
2930 {
2931     foreach (int trackIndex, tracks) {
2932         int mltIndex = m_trackList.at(trackIndex).mlt_index;
2933         QScopedPointer<Mlt::Producer> otherTrack(m_tractor->track(mltIndex));
2934 
2935         if (otherTrack) {
2936             Mlt::Playlist trackPlaylist(*otherTrack);
2937             int idx = trackPlaylist.get_clip_index_at(position);
2938 
2939             if (trackPlaylist.is_blank(idx)) {
2940                 trackPlaylist.resize_clip(idx, 0, trackPlaylist.clip_length(idx) + length - 1);
2941                 QModelIndex modelIndex = createIndex(idx, 0, trackIndex);
2942                 emit dataChanged(modelIndex, modelIndex, QVector<int>() << DurationRole);
2943             } else if (length > 0) {
2944                 int insertBlankAtIdx = idx;
2945                 if (trackPlaylist.clip_start(idx) < position) {
2946                     splitClip(trackIndex, idx, position);
2947                     insertBlankAtIdx = idx + 1;
2948                 }
2949                 beginInsertRows(index(trackIndex), insertBlankAtIdx, insertBlankAtIdx);
2950                 trackPlaylist.insert_blank(insertBlankAtIdx, length - 1);
2951                 endInsertRows();
2952             } else {
2953                 Q_ASSERT(!"unsupported");
2954             }
2955         }
2956     }
2957 }
2958 
mergeClipWithNext(int trackIndex,int clipIndex,bool dryrun)2959 bool MultitrackModel::mergeClipWithNext(int trackIndex, int clipIndex, bool dryrun)
2960 {
2961     int i = m_trackList.at(trackIndex).mlt_index;
2962     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
2963     if (!track)
2964         return false;
2965     Mlt::Playlist playlist(*track);
2966     if (clipIndex >= playlist.count() + 1)
2967         return false;
2968     Mlt::ClipInfo clip1;
2969     Mlt::ClipInfo clip2;
2970     playlist.clip_info(clipIndex, &clip1);
2971     playlist.clip_info(clipIndex + 1, &clip2);
2972 
2973     if (QString::fromUtf8(clip1.resource) != QString::fromUtf8(clip2.resource))
2974         return false;
2975 
2976     if (clip1.frame_out + 1 != clip2.frame_in)
2977         return false;
2978 
2979     if (dryrun)
2980         return true;
2981 
2982     // Consolidate filters
2983     QStringList filters {"fadeInVolume", "fadeOutVolume", "fadeInBrightness", "fadeOutBrightness", "fadeInMovit", "fadeOutMovit"};
2984     for (const auto& s : filters) {
2985         QScopedPointer<Mlt::Filter> filter(getFilter(s, clip1.producer));
2986         if (filter && filter->is_valid()) {
2987             filter.reset(getFilter(s, clip2.producer));
2988             if (filter && filter->is_valid()) {
2989                 clip2.producer->detach(*filter);
2990             }
2991         }
2992     }
2993     Mlt::Controller::copyFilters(*clip2.producer, *clip1.producer);
2994     QModelIndex modelIndex = createIndex(clipIndex, 0, trackIndex);
2995     QVector<int> roles;
2996     roles << FadeInRole;
2997     roles << FadeOutRole;
2998     emit dataChanged(modelIndex, modelIndex, roles);
2999 
3000     liftClip(trackIndex, clipIndex + 1);
3001     trimClipOut(trackIndex, clipIndex, -clip2.frame_count, false, false);
3002 
3003     emit modified();
3004     return true;
3005 }
3006 
isFiltered(Mlt::Producer * producer) const3007 bool MultitrackModel::isFiltered(Mlt::Producer* producer) const
3008 {
3009     if (!producer)
3010         producer = m_tractor;
3011     if (producer && producer->is_valid()) {
3012         int count = producer->filter_count();
3013         for (int i = 0; i < count; i++) {
3014             QScopedPointer<Mlt::Filter> filter(producer->filter(i));
3015             if (filter && filter->is_valid() && !filter->get_int("_loader"))
3016                 return true;
3017         }
3018     }
3019     return false;
3020 }
3021 
getDuration()3022 int MultitrackModel::getDuration()
3023 {
3024     int n = 0;
3025     if (m_tractor) {
3026         foreach (Track t, m_trackList) {
3027             QScopedPointer<Mlt::Producer> track(m_tractor->track(t.mlt_index));
3028             if (track && track->is_valid())
3029                 n = qMax(n, track->get_length());
3030         }
3031     }
3032     return n;
3033 }
3034 
load()3035 void MultitrackModel::load()
3036 {
3037     if (m_tractor) {
3038         beginResetModel();
3039         delete m_tractor;
3040         m_tractor = 0;
3041         m_trackList.clear();
3042         endResetModel();
3043     }
3044     // In some versions of MLT, the resource property is the XML filename,
3045     // but the Mlt::Tractor(Service&) constructor will fail unless it detects
3046     // the type as tractor, and mlt_service_identify() needs the resource
3047     // property to say "<tractor>" to identify it as playlist type.
3048     MLT.producer()->set("mlt_type", "mlt_producer");
3049     MLT.producer()->set("resource", "<tractor>");
3050     MLT.profile().set_explicit(true);
3051     m_tractor = new Mlt::Tractor(*MLT.producer());
3052     if (!m_tractor->is_valid()) {
3053         delete m_tractor;
3054         m_tractor = 0;
3055         return;
3056     }
3057 
3058     loadPlaylist();
3059     addBlackTrackIfNeeded();
3060     MLT.updateAvformatCaching(m_tractor->count());
3061     refreshTrackList();
3062     convertOldDoc();
3063     consolidateBlanksAllTracks();
3064     adjustBackgroundDuration();
3065     adjustTrackFilters();
3066     if (m_trackList.count() > 0) {
3067         beginInsertRows(QModelIndex(), 0, m_trackList.count() - 1);
3068         endInsertRows();
3069         getAudioLevels();
3070     }
3071     emit loaded();
3072     emit filteredChanged();
3073 }
3074 
reload(bool asynchronous)3075 void MultitrackModel::reload(bool asynchronous)
3076 {
3077     if (m_tractor) {
3078         if (asynchronous) {
3079             emit reloadRequested();
3080         } else {
3081             beginResetModel();
3082             endResetModel();
3083             getAudioLevels();
3084             emit filteredChanged();
3085         }
3086     }
3087 }
3088 
replace(int trackIndex,int clipIndex,Mlt::Producer & clip,bool copyFilters)3089 void MultitrackModel::replace(int trackIndex, int clipIndex, Mlt::Producer& clip, bool copyFilters)
3090 {
3091     int i = m_trackList.at(trackIndex).mlt_index;
3092     Mlt::Producer track(m_tractor->track(i));
3093     if (track.is_valid()) {
3094 //        LOG_DEBUG() << __FUNCTION__ << "replace" << position << MLT.XML(&clip);
3095         Mlt::Playlist playlist(track);
3096         int in = clip.get_in();
3097         int out = clip.get_out();
3098         Mlt::Producer oldClip(playlist.get_clip(clipIndex));
3099         Q_ASSERT(oldClip.is_valid());
3100         int clipPlaytime = oldClip.get_playtime();
3101         int transitionIn = oldClip.parent().get(kFilterInProperty)? oldClip.get_in() - oldClip.parent().get_int(kFilterInProperty) : 0;
3102         int transitionOut = oldClip.parent().get(kFilterOutProperty)? oldClip.parent().get_int(kFilterOutProperty) - oldClip.get_out() : 0;
3103 
3104         in += transitionIn;
3105         out -= transitionOut;
3106         if (clip.get_in() > 0 || clip.get_out() == clip.get_playtime() - 1)
3107             out = in + clipPlaytime - 1;
3108         else
3109             in = out - clipPlaytime + 1;
3110         clip.set_in_and_out(in, out);
3111         if (copyFilters) {
3112             auto parent = oldClip.parent();
3113             parent.set(kFilterInProperty, oldClip.get_in());
3114             parent.set(kFilterOutProperty, oldClip.get_out());
3115             Mlt::Controller::copyFilters(parent, clip);
3116             Mlt::Controller::adjustFilters(clip);
3117         }
3118         beginRemoveRows(index(trackIndex), clipIndex, clipIndex);
3119         playlist.remove(clipIndex);
3120         endRemoveRows();
3121         beginInsertRows(index(trackIndex), clipIndex, clipIndex);
3122         playlist.insert_blank(clipIndex, clipPlaytime - 1);
3123         endInsertRows();
3124         overwrite(trackIndex, clip, playlist.clip_start(clipIndex), false);
3125 
3126         // Handle transition on the left
3127         if (transitionIn && isTransition(playlist, clipIndex - 1)) {
3128             Mlt::Producer producer(playlist.get_clip(clipIndex - 1));
3129             if (producer.is_valid()) {
3130                 Mlt::Tractor tractor(MLT_TRACTOR(producer.get_parent()));
3131                 Q_ASSERT(tractor.is_valid());
3132                 Mlt::Producer track(tractor.track(1));
3133                 if (!qstrcmp(track.parent().get(kShotcutHashProperty), oldClip.parent().get(kShotcutHashProperty))) {
3134                     Mlt::Producer cut(clip.cut(in - transitionIn, in - 1));
3135                     tractor.set_track(cut, 1);
3136                 }
3137             }
3138         }
3139         // Handle transition on the right
3140         if (transitionOut && isTransition(playlist, clipIndex + 1)) {
3141             Mlt::Producer producer(playlist.get_clip(clipIndex + 1));
3142             if (producer.is_valid()) {
3143                 Mlt::Tractor tractor(MLT_TRACTOR(producer.get_parent()));
3144                 Q_ASSERT(tractor.is_valid());
3145                 Mlt::Producer track(tractor.track(0));
3146                 if (!qstrcmp(track.parent().get(kShotcutHashProperty), oldClip.parent().get(kShotcutHashProperty))) {
3147                     Mlt::Producer cut(clip.cut(out + 1, out + transitionOut));
3148                     tractor.set_track(cut, 0);
3149                 }
3150             }
3151         }
3152     }
3153 }
3154 
close()3155 void MultitrackModel::close()
3156 {
3157     if (!m_tractor) return;
3158     if (m_trackList.count() > 0) {
3159         beginRemoveRows(QModelIndex(), 0, m_trackList.count() - 1);
3160         m_trackList.clear();
3161         endRemoveRows();
3162     }
3163     delete m_tractor;
3164     m_tractor = 0;
3165     emit closed();
3166 }
3167 
clipIndex(int trackIndex,int position)3168 int MultitrackModel::clipIndex(int trackIndex, int position)
3169 {
3170     int i = m_trackList.at(trackIndex).mlt_index;
3171     QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
3172     if (track) {
3173         Mlt::Playlist playlist(*track);
3174         return playlist.get_clip_index_at(position);
3175     }
3176     return -1; // error
3177 }
3178 
refreshTrackList()3179 void MultitrackModel::refreshTrackList()
3180 {
3181     int n = m_tractor->count();
3182     int a = 0;
3183     int v = 0;
3184     bool isKdenlive = false;
3185 
3186     // Add video tracks in reverse order.
3187     for (int i = 0; i < n; ++i) {
3188         QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
3189         if (!track)
3190             continue;
3191         QString trackId = track->get("id");
3192         if (trackId == "black_track")
3193             isKdenlive = true;
3194         else if (trackId == kBackgroundTrackId)
3195             continue;
3196         else if (!track->get(kShotcutPlaylistProperty) && !track->get(kAudioTrackProperty)) {
3197             int hide = track->get_int("hide");
3198              // hide: 0 = a/v, 2 = muted video track
3199             if (track->get(kVideoTrackProperty) || hide == 0 || hide == 2) {
3200                 Track t;
3201                 t.mlt_index = i;
3202                 t.type = VideoTrackType;
3203                 t.number = v++;
3204                 QString trackName = track->get(kTrackNameProperty);
3205                 if (trackName.isEmpty())
3206                     trackName = QString("V%1").arg(v);
3207                 track->set(kTrackNameProperty, trackName.toUtf8().constData());
3208                 m_trackList.prepend(t);
3209 
3210                 // Always disable compositing on V1.
3211                 if (v == 1) {
3212                     QScopedPointer<Mlt::Transition> transition(getTransition("frei0r.cairoblend", 1));
3213                     if (!transition)
3214                         transition.reset(getTransition("movit.overlay", 1));
3215                     if (transition && transition->is_valid())
3216                         transition->set("disable", 1);
3217                 }
3218             }
3219         }
3220     }
3221 
3222     // Add audio tracks.
3223     for (int i = 0; i < n; ++i) {
3224         QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
3225         if (!track)
3226             continue;
3227         QString trackId = track->get("id");
3228         if (trackId == "black_track")
3229             isKdenlive = true;
3230         else if (isKdenlive && trackId == "playlist1")
3231             // In Kdenlive, playlist1 is a special audio mixdown track.
3232             continue;
3233         else if (trackId == kPlaylistTrackId || trackId == kLegacyPlaylistTrackId)
3234             continue;
3235         else if (!track->get(kShotcutPlaylistProperty) && !track->get(kVideoTrackProperty)) {
3236             int hide = track->get_int("hide");
3237             // hide: 1 = audio only track, 3 = muted audio-only track
3238             if (track->get(kAudioTrackProperty) || hide == 1 || hide == 3) {
3239                 Track t;
3240                 t.mlt_index = i;
3241                 t.type = AudioTrackType;
3242                 t.number = a++;
3243                 QString trackName = track->get(kTrackNameProperty);
3244                 if (trackName.isEmpty())
3245                     trackName = QString("A%1").arg(a);
3246                 track->set(kTrackNameProperty, trackName.toUtf8().constData());
3247                 m_trackList.append(t);
3248 //                LOG_DEBUG() << __FUNCTION__ << QString(track->get("id")) << i;
3249             }
3250         }
3251     }
3252 }
3253 
getAudioLevels()3254 void MultitrackModel::getAudioLevels()
3255 {
3256     for (int trackIx = 0; trackIx < m_trackList.size(); trackIx++) {
3257         int i = m_trackList.at(trackIx).mlt_index;
3258         QScopedPointer<Mlt::Producer> track(m_tractor->track(i));
3259         Mlt::Playlist playlist(*track);
3260         for (int clipIx = 0; clipIx < playlist.count(); clipIx++) {
3261             QScopedPointer<Mlt::Producer> clip(playlist.get_clip(clipIx));
3262             if (clip && clip->is_valid() && !clip->is_blank() && clip->get_int("audio_index") > -1) {
3263                 QModelIndex index = createIndex(clipIx, 0, trackIx);
3264                 AudioLevelsTask::start(clip->parent(), this, index);
3265             }
3266         }
3267     }
3268 }
3269 
addBlackTrackIfNeeded()3270 void MultitrackModel::addBlackTrackIfNeeded()
3271 {
3272     return;
3273     // Make sure the first track is black silence and add it if needed
3274     bool found = false;
3275     int n = m_tractor->count();
3276     QScopedPointer<Mlt::Producer> track(m_tractor->track(0));
3277     if (track) {
3278         Mlt::Playlist playlist(*track);
3279         QScopedPointer<Mlt::Producer> clip(playlist.get_clip(0));
3280         if (clip && QString(clip->get("id")) == "black")
3281             found = true;
3282     }
3283     if (!found) {
3284         // Move all existing tracks down by 1.
3285         for (int i = n; i > 0; i++) {
3286             Mlt::Producer* producer = m_tractor->track(n - 1);
3287             if (producer)
3288                 m_tractor->set_track(*producer, n);
3289             delete producer;
3290         }
3291         Mlt::Producer producer(MLT.profile(), "color:0");
3292         producer.set("mlt_image_format", "rgb24a");
3293         m_tractor->set_track(producer, 0);
3294     }
3295 }
3296 
convertOldDoc()3297 void MultitrackModel::convertOldDoc()
3298 {
3299     // Convert composite to frei0r.cairoblend.
3300     int n = m_tractor->count();
3301     for (int i = 1; i < n; ++i) {
3302         QScopedPointer<Mlt::Transition> transition(getTransition("composite", i));
3303         if (transition) {
3304             Mlt::Transition composite(MLT.profile(), "frei0r.cairoblend");
3305             composite.set("disable", transition->get_int("disable"));
3306             m_tractor->field()->disconnect_service(*transition);
3307             m_tractor->plant_transition(composite, transition->get_int("a_track"), i);
3308         }
3309     }
3310 
3311     // Remove movit.rect filters.
3312     QScopedPointer<Mlt::Service> service(m_tractor->producer());
3313     while (service && service->is_valid()) {
3314         if (service->type() == filter_type) {
3315             Mlt::Filter f((mlt_filter) service->get_service());
3316             if (QString::fromLatin1(f.get("mlt_service")) == "movit.rect") {
3317                 m_tractor->field()->disconnect_service(f);
3318             }
3319         }
3320         service.reset(service->producer());
3321     }
3322 
3323     // Change a_track of composite transitions to bottom video track.
3324     int a_track = 0;
3325     foreach (Track t, m_trackList) {
3326         if (t.type == VideoTrackType)
3327             a_track = t.mlt_index;
3328     }
3329     QString name = Settings.playerGPU()? "movit.overlay" : "frei0r.cairoblend";
3330     foreach (Track t, m_trackList) {
3331         if (t.type == VideoTrackType) {
3332             QScopedPointer<Mlt::Transition> transition(getTransition(name, t.mlt_index));
3333             if (transition && transition->get_a_track() != 0)
3334                 transition->set("a_track", a_track);
3335         }
3336     }
3337 
3338     // Ensure the background track clears the test_audio flag on frames.
3339     QScopedPointer<Mlt::Producer> track(m_tractor->track(0));
3340     if (track) {
3341         Mlt::Playlist playlist(*track);
3342         QScopedPointer<Mlt::ClipInfo> info(playlist.clip_info(0));
3343         if (info && info->producer->is_valid() && QString(info->producer->get("id")) == "black")
3344             info->producer->set("set.test_audio", 0);
3345     }
3346 }
3347 
getTransition(const QString & name,int trackIndex) const3348 Mlt::Transition *MultitrackModel::getTransition(const QString &name, int trackIndex) const
3349 {
3350     QScopedPointer<Mlt::Service> service(m_tractor->producer());
3351     while (service && service->is_valid()) {
3352         if (service->type() == transition_type) {
3353             Mlt::Transition t((mlt_transition) service->get_service());
3354             if (name == t.get("mlt_service") && t.get_b_track() == trackIndex)
3355                 return new Mlt::Transition(t);
3356         }
3357         service.reset(service->producer());
3358     }
3359     return nullptr;
3360 }
3361 
getFilter(const QString & name,int trackIndex) const3362 Mlt::Filter *MultitrackModel::getFilter(const QString &name, int trackIndex) const
3363 {
3364     QScopedPointer<Mlt::Service> service(m_tractor->producer());
3365     while (service && service->is_valid()) {
3366         if (service->type() == filter_type) {
3367             Mlt::Filter f((mlt_filter) service->get_service());
3368             if (name == f.get("mlt_service") && f.get_track() == trackIndex)
3369                 return new Mlt::Filter(f);
3370         }
3371         service.reset(service->producer());
3372     }
3373     return 0;
3374 }
3375 
getFilter(const QString & name,Mlt::Service * service) const3376 Mlt::Filter *MultitrackModel::getFilter(const QString &name, Mlt::Service* service) const
3377 {
3378     return MLT.getFilter(name, service);
3379 }
3380 
removeBlankPlaceholder(Mlt::Playlist & playlist,int trackIndex)3381 void MultitrackModel::removeBlankPlaceholder(Mlt::Playlist& playlist, int trackIndex)
3382 {
3383     if (playlist.count() == 1 && playlist.is_blank(0)) {
3384         beginRemoveRows(index(trackIndex), 0, 0);
3385         playlist.remove(0);
3386         endRemoveRows();
3387     }
3388 }
3389