1 /*
2     SPDX-FileCopyrightText: 2017 Nicolas Carion
3     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
4 */
5 #include "effectstackmodel.hpp"
6 #include "assets/keyframes/model/keyframemodellist.hpp"
7 #include "core.h"
8 #include "mainwindow.h"
9 #include "doc/docundostack.hpp"
10 #include "effectgroupmodel.hpp"
11 #include "effectitemmodel.hpp"
12 #include "effects/effectsrepository.hpp"
13 #include "macros.hpp"
14 #include "timeline2/model/timelinemodel.hpp"
15 #include <profiles/profilemodel.hpp>
16 #include <stack>
17 #include <utility>
18 #include <vector>
19 
EffectStackModel(std::weak_ptr<Mlt::Service> service,ObjectId ownerId,std::weak_ptr<DocUndoStack> undo_stack)20 EffectStackModel::EffectStackModel(std::weak_ptr<Mlt::Service> service, ObjectId ownerId, std::weak_ptr<DocUndoStack> undo_stack)
21     : AbstractTreeModel()
22     , m_effectStackEnabled(true)
23     , m_ownerId(std::move(ownerId))
24     , m_undoStack(std::move(undo_stack))
25     , m_lock(QReadWriteLock::Recursive)
26     , m_loadingExisting(false)
27 {
28     m_masterService = std::move(service);
29 }
30 
construct(std::weak_ptr<Mlt::Service> service,ObjectId ownerId,std::weak_ptr<DocUndoStack> undo_stack)31 std::shared_ptr<EffectStackModel> EffectStackModel::construct(std::weak_ptr<Mlt::Service> service, ObjectId ownerId, std::weak_ptr<DocUndoStack> undo_stack)
32 {
33     std::shared_ptr<EffectStackModel> self(new EffectStackModel(std::move(service), ownerId, std::move(undo_stack)));
34     self->rootItem = EffectGroupModel::construct(QStringLiteral("root"), self, true);
35     return self;
36 }
37 
resetService(std::weak_ptr<Mlt::Service> service)38 void EffectStackModel::resetService(std::weak_ptr<Mlt::Service> service)
39 {
40     QWriteLocker locker(&m_lock);
41     m_masterService = std::move(service);
42     m_childServices.clear();
43     // replant all effects in new service
44     for (int i = 0; i < rootItem->childCount(); ++i) {
45         std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->plant(m_masterService);
46     }
47 }
48 
addService(std::weak_ptr<Mlt::Service> service)49 void EffectStackModel::addService(std::weak_ptr<Mlt::Service> service)
50 {
51     QWriteLocker locker(&m_lock);
52     m_childServices.emplace_back(std::move(service));
53     for (int i = 0; i < rootItem->childCount(); ++i) {
54         std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->plantClone(m_childServices.back());
55     }
56 }
57 
loadService(std::weak_ptr<Mlt::Service> service)58 void EffectStackModel::loadService(std::weak_ptr<Mlt::Service> service)
59 {
60     QWriteLocker locker(&m_lock);
61     m_childServices.emplace_back(std::move(service));
62     for (int i = 0; i < rootItem->childCount(); ++i) {
63         std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->loadClone(m_childServices.back());
64     }
65 }
66 
removeService(const std::shared_ptr<Mlt::Service> & service)67 void EffectStackModel::removeService(const std::shared_ptr<Mlt::Service> &service)
68 {
69     QWriteLocker locker(&m_lock);
70     std::vector<int> to_delete;
71     for (int i = int(m_childServices.size()) - 1; i >= 0; --i) {
72         auto ptr = m_childServices[uint(i)].lock();
73         if (ptr && service->get_int("_childid") == ptr->get_int("_childid")) {
74             for (int j = 0; j < rootItem->childCount(); ++j) {
75                 std::static_pointer_cast<EffectItemModel>(rootItem->child(j))->unplantClone(ptr);
76             }
77             to_delete.push_back(i);
78         }
79     }
80     for (int i : to_delete) {
81         m_childServices.erase(m_childServices.begin() + i);
82     }
83 }
84 
removeCurrentEffect()85 void EffectStackModel::removeCurrentEffect()
86 {
87     int ix = getActiveEffect();
88     if (ix < 0) {
89         return;
90     }
91     std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix));
92     if (effect) {
93         removeEffect(effect);
94     }
95 }
96 
removeAllEffects(Fun & undo,Fun & redo)97 void EffectStackModel::removeAllEffects(Fun &undo, Fun & redo)
98 {
99     QWriteLocker locker(&m_lock);
100     int current = getActiveEffect();
101     while (rootItem->childCount() > 0) {
102         std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(0));
103         int parentId = -1;
104         if (auto ptr = effect->parentItem().lock()) parentId = ptr->getId();
105         Fun local_undo = addItem_lambda(effect, parentId);
106         Fun local_redo = removeItem_lambda(effect->getId());
107         local_redo();
108         UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
109     }
110     std::unordered_set<int> fadeIns = m_fadeIns;
111     std::unordered_set<int> fadeOuts = m_fadeOuts;
112     Fun undo_current = [this, current, fadeIns, fadeOuts]() {
113         if (auto srv = m_masterService.lock()) {
114             srv->set("kdenlive:activeeffect", current);
115         }
116         m_fadeIns = fadeIns;
117         m_fadeOuts = fadeOuts;
118         QVector<int> roles = {TimelineModel::EffectNamesRole};
119         if (!m_fadeIns.empty()) {
120             roles << TimelineModel::FadeInRole;
121         }
122         if (!m_fadeOuts.empty()) {
123             roles << TimelineModel::FadeOutRole;
124         }
125         emit dataChanged(QModelIndex(), QModelIndex(), roles);
126         pCore->updateItemKeyframes(m_ownerId);
127         return true;
128     };
129     Fun redo_current = [this]() {
130         if (auto srv = m_masterService.lock()) {
131             srv->set("kdenlive:activeeffect", -1);
132         }
133         QVector<int> roles = {TimelineModel::EffectNamesRole};
134         if (!m_fadeIns.empty()) {
135             roles << TimelineModel::FadeInRole;
136         }
137         if (!m_fadeOuts.empty()) {
138             roles << TimelineModel::FadeOutRole;
139         }
140         m_fadeIns.clear();
141         m_fadeOuts.clear();
142         emit dataChanged(QModelIndex(), QModelIndex(), roles);
143         pCore->updateItemKeyframes(m_ownerId);
144         return true;
145     };
146     redo_current();
147     PUSH_LAMBDA(redo_current, redo);
148     PUSH_LAMBDA(undo_current, undo);
149 }
150 
removeEffect(const std::shared_ptr<EffectItemModel> & effect)151 void EffectStackModel::removeEffect(const std::shared_ptr<EffectItemModel> &effect)
152 {
153     qDebug() << "* * ** REMOVING EFFECT FROM STACK!!!\n!!!!!!!!!";
154     QWriteLocker locker(&m_lock);
155     Q_ASSERT(m_allItems.count(effect->getId()) > 0);
156     int parentId = -1;
157     if (auto ptr = effect->parentItem().lock()) parentId = ptr->getId();
158     int current = getActiveEffect();
159     if (current >= rootItem->childCount() - 1) {
160         current--;
161     }
162     setActiveEffect(current);
163     int currentRow = effect->row();
164     Fun undo = addItem_lambda(effect, parentId);
165     if (currentRow != rowCount() - 1) {
166         Fun move = moveItem_lambda(effect->getId(), currentRow, true);
167         PUSH_LAMBDA(move, undo);
168     }
169     Fun redo = removeItem_lambda(effect->getId());
170     bool res = redo();
171     if (res) {
172         int inFades = int(m_fadeIns.size());
173         int outFades = int(m_fadeOuts.size());
174         m_fadeIns.erase(effect->getId());
175         m_fadeOuts.erase(effect->getId());
176         inFades = int(m_fadeIns.size()) - inFades;
177         outFades = int(m_fadeOuts.size()) - outFades;
178         QString effectName = EffectsRepository::get()->getName(effect->getAssetId());
179         Fun update = [this, current, inFades, outFades]() {
180             // Required to build the effect view
181             if (current < 0 || rowCount() == 0) {
182                 // Stack is now empty
183                 emit dataChanged(QModelIndex(), QModelIndex(), {});
184             } else {
185                 QVector<int> roles = {TimelineModel::EffectNamesRole};
186                 if (inFades < 0) {
187                     roles << TimelineModel::FadeInRole;
188                 }
189                 if (outFades < 0) {
190                     roles << TimelineModel::FadeOutRole;
191                 }
192                 emit dataChanged(QModelIndex(), QModelIndex(), roles);
193             }
194             // TODO: only update if effect is fade or keyframe
195             /*if (inFades < 0) {
196                 pCore->updateItemModel(m_ownerId, QStringLiteral("fadein"));
197             } else if (outFades < 0) {
198                 pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout"));
199             }*/
200             updateEffectZones();
201             pCore->updateItemKeyframes(m_ownerId);
202             return true;
203         };
204         Fun update2 = [this, inFades, outFades]() {
205             // Required to build the effect view
206             QVector<int> roles = {TimelineModel::EffectNamesRole};
207             // TODO: only update if effect is fade or keyframe
208             if (inFades < 0) {
209                 roles << TimelineModel::FadeInRole;
210             } else if (outFades < 0) {
211                 roles << TimelineModel::FadeOutRole;
212             }
213             emit dataChanged(QModelIndex(), QModelIndex(), roles);
214             updateEffectZones();
215             pCore->updateItemKeyframes(m_ownerId);
216             return true;
217         };
218         update();
219         PUSH_LAMBDA(update, redo);
220         PUSH_LAMBDA(update2, undo);
221         PUSH_UNDO(undo, redo, i18n("Delete effect %1", effectName));
222     } else {
223         qDebug() << "..........FAILED EFFECT DELETION";
224     }
225 }
226 
copyXmlEffect(QDomElement effect)227 bool EffectStackModel::copyXmlEffect(QDomElement effect)
228 {
229     std::function<bool(void)> undo = []() { return true; };
230     std::function<bool(void)> redo = []() { return true; };
231     bool result = fromXml(effect, undo, redo);
232     if (result) {
233         PUSH_UNDO(undo, redo, i18n("Copy effect"));
234     }
235     return result;
236 }
237 
toXml(QDomDocument & document)238 QDomElement EffectStackModel::toXml(QDomDocument &document)
239 {
240     QDomElement container = document.createElement(QStringLiteral("effects"));
241     int currentIn = pCore->getItemIn(m_ownerId);
242     container.setAttribute(QStringLiteral("parentIn"), currentIn);
243     for (int i = 0; i < rootItem->childCount(); ++i) {
244         std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
245         QDomElement sub = document.createElement(QStringLiteral("effect"));
246         sub.setAttribute(QStringLiteral("id"), sourceEffect->getAssetId());
247         int filterIn = sourceEffect->filter().get_int("in");
248         int filterOut = sourceEffect->filter().get_int("out");
249         if (filterOut > filterIn) {
250             sub.setAttribute(QStringLiteral("in"), filterIn);
251             sub.setAttribute(QStringLiteral("out"), filterOut);
252         }
253         QStringList passProps {QStringLiteral("disable"), QStringLiteral("kdenlive:collapsed")};
254         for (const QString &param : passProps) {
255             int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData());
256             if (paramVal > 0) {
257                 Xml::setXmlProperty(sub, param, QString::number(paramVal));
258             }
259         }
260         QVector<QPair<QString, QVariant>> params = sourceEffect->getAllParameters();
261         for (const auto &param : qAsConst(params)) {
262             Xml::setXmlProperty(sub, param.first, param.second.toString());
263         }
264         container.appendChild(sub);
265     }
266     return container;
267 }
268 
rowToXml(int row,QDomDocument & document)269 QDomElement EffectStackModel::rowToXml(int row, QDomDocument &document)
270 {
271     QDomElement container = document.createElement(QStringLiteral("effects"));
272     if (row < 0 || row >= rootItem->childCount()) {
273         return container;
274     }
275     int currentIn = pCore->getItemIn(m_ownerId);
276     container.setAttribute(QStringLiteral("parentIn"), currentIn);
277     std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(row));
278     QDomElement sub = document.createElement(QStringLiteral("effect"));
279     sub.setAttribute(QStringLiteral("id"), sourceEffect->getAssetId());
280     int filterIn = sourceEffect->filter().get_int("in");
281     int filterOut = sourceEffect->filter().get_int("out");
282     if (filterOut > filterIn) {
283         sub.setAttribute(QStringLiteral("in"), filterIn);
284         sub.setAttribute(QStringLiteral("out"), filterOut);
285     }
286     QStringList passProps {QStringLiteral("disable"), QStringLiteral("kdenlive:collapsed")};
287     for (const QString &param : passProps) {
288         int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData());
289         if (paramVal > 0) {
290             Xml::setXmlProperty(sub, param, QString::number(paramVal));
291         }
292     }
293     QVector<QPair<QString, QVariant>> params = sourceEffect->getAllParameters();
294     for (const auto &param : qAsConst(params)) {
295         Xml::setXmlProperty(sub, param.first, param.second.toString());
296     }
297     container.appendChild(sub);
298     return container;
299 }
300 
fromXml(const QDomElement & effectsXml,Fun & undo,Fun & redo)301 bool EffectStackModel::fromXml(const QDomElement &effectsXml, Fun &undo, Fun &redo)
302 {
303     QDomNodeList nodeList = effectsXml.elementsByTagName(QStringLiteral("effect"));
304     int parentIn = effectsXml.attribute(QStringLiteral("parentIn")).toInt();
305     qDebug()<<"// GOT PREVIOUS PARENTIN: "<<parentIn<<"\n\n=======\n=======\n\n";
306     int currentIn = pCore->getItemIn(m_ownerId);
307     PlaylistState::ClipState state = pCore->getItemState(m_ownerId);
308     bool effectAdded = false;
309     for (int i = 0; i < nodeList.count(); ++i) {
310         QDomElement node = nodeList.item(i).toElement();
311         const QString effectId = node.attribute(QStringLiteral("id"));
312         AssetListType::AssetType type = EffectsRepository::get()->getType(effectId);
313         bool isAudioEffect = type == AssetListType::AssetType::Audio || type == AssetListType::AssetType::CustomAudio;
314         if (isAudioEffect) {
315             if (state != PlaylistState::AudioOnly) {
316                 continue;
317             }
318         } else if (state != PlaylistState::VideoOnly) {
319             continue;
320         }
321         if (m_ownerId.first == ObjectType::TimelineClip && EffectsRepository::get()->isUnique(effectId) && hasEffect(effectId))  {
322             pCore->displayMessage(i18n("Effect %1 cannot be added twice.", EffectsRepository::get()->getName(effectId)), ErrorMessage);
323             return false;
324         }
325         bool effectEnabled = true;
326         if (Xml::hasXmlProperty(node, QLatin1String("disable"))) {
327             effectEnabled = Xml::getXmlProperty(node, QLatin1String("disable")).toInt() != 1;
328         }
329         auto effect = EffectItemModel::construct(effectId, shared_from_this(), effectEnabled);
330         const QString in = node.attribute(QStringLiteral("in"));
331         const QString out = node.attribute(QStringLiteral("out"));
332         if (!out.isEmpty()) {
333             effect->filter().set("in", in.toUtf8().constData());
334             effect->filter().set("out", out.toUtf8().constData());
335         }
336         QStringList keyframeParams = effect->getKeyframableParameters();
337         QVector<QPair<QString, QVariant>> parameters;
338         QDomNodeList params = node.elementsByTagName(QStringLiteral("property"));
339         for (int j = 0; j < params.count(); j++) {
340             QDomElement pnode = params.item(j).toElement();
341             const QString pName = pnode.attribute(QStringLiteral("name"));
342             if (pName == QLatin1String("in") || pName == QLatin1String("out")) {
343                 continue;
344             }
345             if (keyframeParams.contains(pName)) {
346                 // This is a keyframable parameter, fix offset
347                 QString pValue = KeyframeModel::getAnimationStringWithOffset(effect, pnode.text(), currentIn - parentIn);
348                 parameters.append(QPair<QString, QVariant>(pName, QVariant(pValue)));
349             } else {
350                 parameters.append(QPair<QString, QVariant>(pName, QVariant(pnode.text())));
351             }
352         }
353         effect->setParameters(parameters);
354         Fun local_undo = removeItem_lambda(effect->getId());
355         // TODO the parent should probably not always be the root
356         Fun local_redo = addItem_lambda(effect, rootItem->getId());
357         effect->prepareKeyframes();
358         connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
359         connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
360         connect(effect.get(), &AssetParameterModel::showEffectZone, this, &EffectStackModel::updateEffectZones);
361         if (effectId.startsWith(QLatin1String("fadein")) || effectId.startsWith(QLatin1String("fade_from_"))) {
362             m_fadeIns.insert(effect->getId());
363             int duration = effect->filter().get_length() - 1;
364             effect->filter().set("in", currentIn);
365             effect->filter().set("out", currentIn + duration);
366             if (effectId.startsWith(QLatin1String("fade_"))) {
367                 if (effect->filter().get("alpha") == QLatin1String("1")) {
368                     // Adjust level value to match filter end
369                     effect->filter().set("level", "0=0;-1=1");
370                 } else if (effect->filter().get("level") == QLatin1String("1")) {
371                     effect->filter().set("alpha", "0=0;-1=1");
372                 }
373             }
374         } else if (effectId.startsWith(QLatin1String("fadeout")) || effectId.startsWith(QLatin1String("fade_to_"))) {
375             m_fadeOuts.insert(effect->getId());
376             int duration = effect->filter().get_length() - 1;
377             int filterOut = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1;
378             effect->filter().set("in", filterOut - duration);
379             effect->filter().set("out", filterOut);
380             if (effectId.startsWith(QLatin1String("fade_"))) {
381                 if (effect->filter().get("alpha") == QLatin1String("1")) {
382                     // Adjust level value to match filter end
383                     effect->filter().set("level", "0=1;-1=0");
384                 } else if (effect->filter().get("level") == QLatin1String("1")) {
385                     effect->filter().set("alpha", "0=1;-1=0");
386                 }
387             }
388         }
389         local_redo();
390         effectAdded = true;
391         UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
392     }
393     if (effectAdded) {
394         Fun update = [this]() {
395             emit dataChanged(QModelIndex(), QModelIndex(), {});
396             return true;
397         };
398         update();
399         PUSH_LAMBDA(update, redo);
400         PUSH_LAMBDA(update, undo);
401     }
402     return effectAdded;
403 }
404 
copyEffect(const std::shared_ptr<AbstractEffectItem> & sourceItem,PlaylistState::ClipState state,bool logUndo)405 bool EffectStackModel::copyEffect(const std::shared_ptr<AbstractEffectItem> &sourceItem, PlaylistState::ClipState state, bool logUndo)
406 {
407     QWriteLocker locker(&m_lock);
408     if (sourceItem->childCount() > 0) {
409         // TODO: group
410         return false;
411     }
412     bool audioEffect = sourceItem->isAudio();
413     if (audioEffect) {
414         if (state == PlaylistState::VideoOnly) {
415             // This effect cannot be used
416             return false;
417         }
418     } else if (state == PlaylistState::AudioOnly) {
419         return false;
420     }
421     std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(sourceItem);
422     const QString effectId = sourceEffect->getAssetId();
423     if (m_ownerId.first == ObjectType::TimelineClip && EffectsRepository::get()->isUnique(effectId) && hasEffect(effectId))  {
424         pCore->displayMessage(i18n("Effect %1 cannot be added twice.", EffectsRepository::get()->getName(effectId)), ErrorMessage);
425         return false;
426     }
427     bool enabled = sourceEffect->isEnabled();
428     auto effect = EffectItemModel::construct(effectId, shared_from_this(), enabled);
429     effect->setParameters(sourceEffect->getAllParameters());
430     if (!enabled) {
431         effect->filter().set("disable", 1);
432     }
433     effect->filter().set("in", sourceEffect->filter().get_int("in"));
434     effect->filter().set("out", sourceEffect->filter().get_int("out"));
435     Fun local_undo = removeItem_lambda(effect->getId());
436     // TODO the parent should probably not always be the root
437     Fun local_redo = addItem_lambda(effect, rootItem->getId());
438     effect->prepareKeyframes();
439     connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
440     connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
441     connect(effect.get(), &AssetParameterModel::showEffectZone, this, &EffectStackModel::updateEffectZones);
442     QVector<int> roles = {TimelineModel::EffectNamesRole};
443     if (effectId.startsWith(QLatin1String("fadein")) || effectId.startsWith(QLatin1String("fade_from_"))) {
444         m_fadeIns.insert(effect->getId());
445         int duration = effect->filter().get_length() - 1;
446         int in = pCore->getItemIn(m_ownerId);
447         effect->filter().set("in", in);
448         effect->filter().set("out", in + duration);
449         roles << TimelineModel::FadeInRole;
450     } else if (effectId.startsWith(QLatin1String("fadeout")) || effectId.startsWith(QLatin1String("fade_to_"))) {
451         m_fadeOuts.insert(effect->getId());
452         int duration = effect->filter().get_length() - 1;
453         int out = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1;
454         effect->filter().set("in", out - duration);
455         effect->filter().set("out", out);
456         roles << TimelineModel::FadeOutRole;
457     }
458     bool res = local_redo();
459     if (res) {
460         Fun update = [this, roles]() {
461             emit dataChanged(QModelIndex(), QModelIndex(), roles);
462             return true;
463         };
464         update();
465         if (logUndo) {
466             PUSH_LAMBDA(update, local_redo);
467             PUSH_LAMBDA(update, local_undo);
468             pCore->pushUndo(local_undo, local_redo, i18n("Paste effect"));
469         }
470     }
471     return res;
472 }
473 
appendEffect(const QString & effectId,bool makeCurrent)474 bool EffectStackModel::appendEffect(const QString &effectId, bool makeCurrent)
475 {
476     QWriteLocker locker(&m_lock);
477     if (m_ownerId.first == ObjectType::TimelineClip && EffectsRepository::get()->isUnique(effectId) && hasEffect(effectId))  {
478         pCore->displayMessage(i18n("Effect %1 cannot be added twice.", EffectsRepository::get()->getName(effectId)), ErrorMessage);
479         return false;
480     }
481     std::unordered_set<int> previousFadeIn = m_fadeIns;
482     std::unordered_set<int> previousFadeOut = m_fadeOuts;
483     if (EffectsRepository::get()->isGroup(effectId)) {
484         QDomElement doc = EffectsRepository::get()->getXml(effectId);
485         return copyXmlEffect(doc);
486     }
487     auto effect = EffectItemModel::construct(effectId, shared_from_this());
488     PlaylistState::ClipState state = pCore->getItemState(m_ownerId);
489     if (effect->isAudio()) {
490         if (state == PlaylistState::VideoOnly) {
491             // Cannot add effect to this clip
492             pCore->displayMessage(i18n("Cannot add effect to clip"), ErrorMessage);
493             return false;
494         }
495     } else if (state == PlaylistState::AudioOnly) {
496         // Cannot add effect to this clip
497         pCore->displayMessage(i18n("Cannot add effect to clip"), ErrorMessage);
498         return false;
499     }
500     Fun undo = removeItem_lambda(effect->getId());
501     // TODO the parent should probably not always be the root
502     Fun redo = addItem_lambda(effect, rootItem->getId());
503     effect->prepareKeyframes();
504     connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
505     connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
506     connect(effect.get(), &AssetParameterModel::showEffectZone, this, &EffectStackModel::updateEffectZones);
507     int currentActive = getActiveEffect();
508     if (makeCurrent) {
509         setActiveEffect(rowCount());
510     }
511     bool res = redo();
512     if (res) {
513         int inFades = 0;
514         int outFades = 0;
515         if (effectId.startsWith(QLatin1String("fadein")) || effectId.startsWith(QLatin1String("fade_from_"))) {
516             int duration = effect->filter().get_length() - 1;
517             int in = pCore->getItemIn(m_ownerId);
518             effect->filter().set("in", in);
519             effect->filter().set("out", in + duration);
520             inFades++;
521         } else if (effectId.startsWith(QLatin1String("fadeout")) || effectId.startsWith(QLatin1String("fade_to_"))) {
522             /*int duration = effect->filter().get_length() - 1;
523             int out = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1;
524             effect->filter().set("in", out - duration);
525             effect->filter().set("out", out);*/
526             outFades++;
527         } else if (m_ownerId.first == ObjectType::TimelineTrack) {
528             effect->filter().set("out", pCore->getItemDuration(m_ownerId));
529         }
530         Fun update = [this, inFades, outFades]() {
531             // TODO: only update if effect is fade or keyframe
532             QVector<int> roles = {TimelineModel::EffectNamesRole};
533             if (inFades > 0) {
534                 roles << TimelineModel::FadeInRole;
535             } else if (outFades > 0) {
536                 roles << TimelineModel::FadeOutRole;
537             }
538             pCore->updateItemKeyframes(m_ownerId);
539             emit dataChanged(QModelIndex(), QModelIndex(), roles);
540             return true;
541         };
542         Fun update_undo = [this, inFades, outFades, previousFadeIn, previousFadeOut]() {
543             // TODO: only update if effect is fade or keyframe
544             QVector<int> roles = {TimelineModel::EffectNamesRole};
545             if (inFades > 0) {
546                 m_fadeIns = previousFadeIn;
547                 roles << TimelineModel::FadeInRole;
548             } else if (outFades > 0) {
549                 m_fadeOuts = previousFadeOut;
550                 roles << TimelineModel::FadeOutRole;
551             }
552             pCore->updateItemKeyframes(m_ownerId);
553             emit dataChanged(QModelIndex(), QModelIndex(), roles);
554             return true;
555         };
556         update();
557         PUSH_LAMBDA(update, redo);
558         PUSH_LAMBDA(update_undo, undo);
559         PUSH_UNDO(undo, redo, i18n("Add effect %1", EffectsRepository::get()->getName(effectId)));
560     } else if (makeCurrent) {
561         setActiveEffect(currentActive);
562     }
563     return res;
564 }
565 
adjustStackLength(bool adjustFromEnd,int oldIn,int oldDuration,int newIn,int duration,int offset,Fun & undo,Fun & redo,bool logUndo)566 bool EffectStackModel::adjustStackLength(bool adjustFromEnd, int oldIn, int oldDuration, int newIn, int duration, int offset, Fun &undo, Fun &redo,
567                                          bool logUndo)
568 {
569     QWriteLocker locker(&m_lock);
570     const int fadeInDuration = getFadePosition(true);
571     const int fadeOutDuration = getFadePosition(false);
572     int out = newIn + duration;
573     for (const auto &leaf : rootItem->getLeaves()) {
574         std::shared_ptr<AbstractEffectItem> item = std::static_pointer_cast<AbstractEffectItem>(leaf);
575         if (item->effectItemType() == EffectItemType::Group) {
576             // probably an empty group, ignore
577             continue;
578         }
579         std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(leaf);
580         if (fadeInDuration > 0 && m_fadeIns.count(leaf->getId()) > 0) {
581             // Adjust fade in
582             int oldEffectIn = qMax(0, effect->filter().get_in());
583             int oldEffectOut = effect->filter().get_out();
584             qDebug() << "--previous effect: " << oldEffectIn << "-" << oldEffectOut;
585             int effectDuration = qMin(effect->filter().get_length() - 1, duration);
586             if (!adjustFromEnd && (oldIn != newIn || duration != oldDuration)) {
587                 // Clip start was resized, adjust effect in / out
588                 Fun operation = [effect, newIn, effectDuration, logUndo]() {
589                     effect->setParameter(QStringLiteral("in"), newIn, false);
590                     effect->setParameter(QStringLiteral("out"), newIn + effectDuration, logUndo);
591                     qDebug() << "--new effect: " << newIn << "-" << newIn + effectDuration;
592                     return true;
593                 };
594                 bool res = operation();
595                 if (!res) {
596                     return false;
597                 }
598                 Fun reverse = [effect, oldEffectIn, oldEffectOut, logUndo]() {
599                     effect->setParameter(QStringLiteral("in"), oldEffectIn, false);
600                     effect->setParameter(QStringLiteral("out"), oldEffectOut, logUndo);
601                     return true;
602                 };
603                 PUSH_LAMBDA(operation, redo);
604                 PUSH_LAMBDA(reverse, undo);
605             } else if (effectDuration < oldEffectOut - oldEffectIn || (logUndo && effect->filter().get_int("_refout") > 0)) {
606                 // Clip length changed, shorter than effect length so resize
607                 int referenceEffectOut = effect->filter().get_int("_refout");
608                 if (referenceEffectOut <= 0) {
609                     referenceEffectOut = oldEffectOut;
610                     effect->filter().set("_refout", referenceEffectOut);
611                 }
612                 Fun operation = [effect, oldEffectIn, effectDuration, logUndo]() {
613                     effect->setParameter(QStringLiteral("out"), oldEffectIn + effectDuration, logUndo);
614                     return true;
615                 };
616                 bool res = operation();
617                 if (!res) {
618                     return false;
619                 }
620                 if (logUndo) {
621                     Fun reverse = [effect, referenceEffectOut]() {
622                         effect->setParameter(QStringLiteral("out"), referenceEffectOut, true);
623                         effect->filter().set("_refout", nullptr);
624                         return true;
625                     };
626                     PUSH_LAMBDA(operation, redo);
627                     PUSH_LAMBDA(reverse, undo);
628                 }
629             }
630         } else if (fadeOutDuration > 0 && m_fadeOuts.count(leaf->getId()) > 0) {
631             // Adjust fade out
632             int effectDuration = qMin(fadeOutDuration, duration);
633             int newFadeIn = out - effectDuration;
634             int oldFadeIn = effect->filter().get_int("in");
635             int oldOut = effect->filter().get_int("out");
636             int referenceEffectIn = effect->filter().get_int("_refin");
637             if (referenceEffectIn <= 0) {
638                 referenceEffectIn = oldFadeIn;
639                 effect->filter().set("_refin", referenceEffectIn);
640             }
641             Fun operation = [effect, newFadeIn, out, logUndo]() {
642                 effect->setParameter(QStringLiteral("in"), newFadeIn, false);
643                 effect->setParameter(QStringLiteral("out"), out, logUndo);
644                 return true;
645             };
646             bool res = operation();
647             if (!res) {
648                 return false;
649             }
650             if (logUndo) {
651                 Fun reverse = [effect, referenceEffectIn, oldOut]() {
652                     effect->setParameter(QStringLiteral("in"), referenceEffectIn, false);
653                     effect->setParameter(QStringLiteral("out"), oldOut, true);
654                     effect->filter().set("_refin", nullptr);
655                     return true;
656                 };
657                 PUSH_LAMBDA(operation, redo);
658                 PUSH_LAMBDA(reverse, undo);
659             }
660         } else {
661             // Not a fade effect, check for keyframes
662             bool hasZone = effect->filter().get_int("kdenlive:force_in_out") == 1;
663             std::shared_ptr<KeyframeModelList> keyframes = effect->getKeyframeModel();
664             if (keyframes != nullptr) {
665                 // Effect has keyframes, update these
666                 keyframes->resizeKeyframes(oldIn, oldIn + oldDuration, newIn, out - 1, offset, adjustFromEnd, undo, redo);
667                 QModelIndex index = getIndexFromItem(effect);
668                 Fun refresh = [effect, index]() {
669                     emit effect->dataChanged(index, index, QVector<int>());
670                     return true;
671                 };
672                 refresh();
673                 PUSH_LAMBDA(refresh, redo);
674                 PUSH_LAMBDA(refresh, undo);
675             } else {
676                 qDebug() << "// NULL Keyframes---------";
677             }
678             if (m_ownerId.first == ObjectType::TimelineTrack && !hasZone) {
679                 int oldEffectOut = effect->filter().get_out();
680                 Fun operation = [effect, out, logUndo]() {
681                     effect->setParameter(QStringLiteral("out"), out, logUndo);
682                     return true;
683                 };
684                 bool res = operation();
685                 if (!res) {
686                     return false;
687                 }
688                 if (logUndo) {
689                     Fun reverse = [effect, oldEffectOut]() {
690                         effect->setParameter(QStringLiteral("out"), oldEffectOut, true);
691                         return true;
692                     };
693                     PUSH_LAMBDA(operation, redo);
694                     PUSH_LAMBDA(reverse, undo);
695                 }
696             }
697         }
698     }
699     return true;
700 }
701 
adjustFadeLength(int duration,bool fromStart,bool audioFade,bool videoFade,bool logUndo)702 bool EffectStackModel::adjustFadeLength(int duration, bool fromStart, bool audioFade, bool videoFade, bool logUndo)
703 {
704     QWriteLocker locker(&m_lock);
705     if (fromStart) {
706         // Fade in
707         if (m_fadeIns.empty()) {
708             if (audioFade) {
709                 appendEffect(QStringLiteral("fadein"));
710             }
711             if (videoFade) {
712                 appendEffect(QStringLiteral("fade_from_black"));
713             }
714         }
715         QList<QModelIndex> indexes;
716         auto ptr = m_masterService.lock();
717         int in = 0;
718         if (ptr) {
719             in = ptr->get_int("in");
720         }
721         int oldDuration = -1;
722         for (int i = 0; i < rootItem->childCount(); ++i) {
723             if (m_fadeIns.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0) {
724                 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
725                 if (oldDuration == -1) {
726                     oldDuration = effect->filter().get_length();
727                 }
728                 effect->filter().set("in", in);
729                 duration = qMin(pCore->getItemDuration(m_ownerId), duration);
730                 effect->filter().set("out", in + duration);
731                 indexes << getIndexFromItem(effect);
732                 if (effect->filter().get("alpha") == QLatin1String("1")) {
733                     // Adjust level value to match filter end
734                     effect->filter().set("level", "0=0;-1=1");
735                 } else if (effect->filter().get("level") == QLatin1String("1")) {
736                     effect->filter().set("alpha", "0=0;-1=1");
737                 }
738             }
739         }
740         if (!indexes.isEmpty()) {
741             emit dataChanged(indexes.first(), indexes.last(), QVector<int>());
742             pCore->updateItemModel(m_ownerId, QStringLiteral("fadein"));
743             if (videoFade) {
744                 int min = pCore->getItemPosition(m_ownerId);
745                 QPair<int, int> range = {min, min + qMax(duration, oldDuration)};
746                 pCore->refreshProjectRange(range);
747                 if (logUndo) {
748                     pCore->invalidateRange(range);
749                 }
750             }
751         }
752     } else {
753         // Fade out
754         if (m_fadeOuts.empty()) {
755             if (audioFade) {
756                 appendEffect(QStringLiteral("fadeout"));
757             }
758             if (videoFade) {
759                 appendEffect(QStringLiteral("fade_to_black"));
760             }
761         }
762         int in = 0;
763         auto ptr = m_masterService.lock();
764         if (ptr) {
765             in = ptr->get_int("in");
766         }
767         int itemDuration = pCore->getItemDuration(m_ownerId);
768         int out = in + itemDuration - 1;
769         int oldDuration = -1;
770         QList<QModelIndex> indexes;
771         for (int i = 0; i < rootItem->childCount(); ++i) {
772             if (m_fadeOuts.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0) {
773                 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
774                 if (oldDuration == -1) {
775                     oldDuration = effect->filter().get_length();
776                 }
777                 effect->filter().set("out", out);
778                 duration = qMin(itemDuration, duration);
779                 effect->filter().set("in", out - duration);
780                 indexes << getIndexFromItem(effect);
781                 if (effect->filter().get("alpha") == QLatin1String("1")) {
782                     // Adjust level value to match filter end
783                     effect->filter().set("level", "0=1;-1=0");
784                 } else if (effect->filter().get("level") == QLatin1String("1")) {
785                     effect->filter().set("alpha", "0=1;-1=0");
786                 }
787             }
788         }
789         if (!indexes.isEmpty()) {
790             emit dataChanged(indexes.first(), indexes.last(), QVector<int>());
791             pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout"));
792             if (videoFade) {
793                 int min = pCore->getItemPosition(m_ownerId);
794                 QPair<int, int> range = {min + itemDuration - qMax(duration, oldDuration), min + itemDuration};
795                 pCore->refreshProjectRange(range);
796                 if (logUndo) {
797                     pCore->invalidateRange(range);
798                 }
799             }
800         }
801     }
802     return true;
803 }
804 
getFadePosition(bool fromStart)805 int EffectStackModel::getFadePosition(bool fromStart)
806 {
807     QWriteLocker locker(&m_lock);
808     if (fromStart) {
809         if (m_fadeIns.empty()) {
810             return 0;
811         }
812         for (int i = 0; i < rootItem->childCount(); ++i) {
813             if (*(m_fadeIns.begin()) == std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) {
814                 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
815                 return effect->filter().get_length() - 1;
816             }
817         }
818     } else {
819         if (m_fadeOuts.empty()) {
820             return 0;
821         }
822         for (int i = 0; i < rootItem->childCount(); ++i) {
823             if (*(m_fadeOuts.begin()) == std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) {
824                 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
825                 return effect->filter().get_length() - 1;
826             }
827         }
828     }
829     return 0;
830 }
831 
removeFade(bool fromStart)832 bool EffectStackModel::removeFade(bool fromStart)
833 {
834     QWriteLocker locker(&m_lock);
835     std::vector<int> toRemove;
836     for (int i = 0; i < rootItem->childCount(); ++i) {
837         if ((fromStart && m_fadeIns.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0) ||
838             (!fromStart && m_fadeOuts.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0)) {
839             toRemove.push_back(i);
840         }
841     }
842     // Let's put index in reverse order so we don't mess when deleting
843     std::reverse( toRemove.begin(), toRemove.end() );
844     for (int i : toRemove) {
845         std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
846         removeEffect(effect);
847     }
848     return true;
849 }
850 
moveEffect(int destRow,const std::shared_ptr<AbstractEffectItem> & item)851 void EffectStackModel::moveEffect(int destRow, const std::shared_ptr<AbstractEffectItem> &item)
852 {
853     QWriteLocker locker(&m_lock);
854     Q_ASSERT(m_allItems.count(item->getId()) > 0);
855     int oldRow = item->row();
856     Fun undo = moveItem_lambda(item->getId(), oldRow);
857     Fun redo = moveItem_lambda(item->getId(), destRow);
858     bool res = redo();
859     if (res) {
860         Fun update = [this]() {
861             emit this->dataChanged(QModelIndex(), QModelIndex(), {TimelineModel::EffectNamesRole});
862             return true;
863         };
864         update();
865         UPDATE_UNDO_REDO(update, update, undo, redo);
866         auto effectId = std::static_pointer_cast<EffectItemModel>(item)->getAssetId();
867         PUSH_UNDO(undo, redo, i18n("Move effect %1", EffectsRepository::get()->getName(effectId)));
868     }
869 }
870 
registerItem(const std::shared_ptr<TreeItem> & item)871 void EffectStackModel::registerItem(const std::shared_ptr<TreeItem> &item)
872 {
873     QWriteLocker locker(&m_lock);
874     // qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting effect";
875     QModelIndex ix;
876     if (!item->isRoot()) {
877         auto effectItem = std::static_pointer_cast<EffectItemModel>(item);
878         if (!m_loadingExisting) {
879             // qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting effect in " << m_childServices.size();
880             effectItem->plant(m_masterService);
881             for (const auto &service : m_childServices) {
882                 // qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting CLONE effect in " << (void *)service.lock().get();
883                 effectItem->plantClone(service);
884             }
885         }
886         effectItem->setEffectStackEnabled(m_effectStackEnabled);
887         const QString &effectId = effectItem->getAssetId();
888         if (effectId.startsWith(QLatin1String("fadein")) || effectId.startsWith(QLatin1String("fade_from_"))) {
889             m_fadeIns.insert(effectItem->getId());
890         } else if (effectId.startsWith(QLatin1String("fadeout")) || effectId.startsWith(QLatin1String("fade_to_"))) {
891             m_fadeOuts.insert(effectItem->getId());
892         }
893         ix = getIndexFromItem(effectItem);
894         if (!effectItem->isAudio() && !m_loadingExisting) {
895             pCore->refreshProjectItem(m_ownerId);
896             pCore->invalidateItem(m_ownerId);
897         }
898     }
899     AbstractTreeModel::registerItem(item);
900 }
901 
deregisterItem(int id,TreeItem * item)902 void EffectStackModel::deregisterItem(int id, TreeItem *item)
903 {
904     QWriteLocker locker(&m_lock);
905     if (!item->isRoot()) {
906         auto effectItem = static_cast<AbstractEffectItem *>(item);
907         effectItem->unplant(m_masterService);
908         for (const auto &service : m_childServices) {
909             effectItem->unplantClone(service);
910         }
911         if (!effectItem->isAudio()) {
912             pCore->refreshProjectItem(m_ownerId);
913             pCore->invalidateItem(m_ownerId);
914         }
915     }
916     AbstractTreeModel::deregisterItem(id, item);
917 }
918 
setEffectStackEnabled(bool enabled)919 void EffectStackModel::setEffectStackEnabled(bool enabled)
920 {
921     QWriteLocker locker(&m_lock);
922     m_effectStackEnabled = enabled;
923 
924     QList<QModelIndex> indexes;
925     // Recursively updates children states
926     for (int i = 0; i < rootItem->childCount(); ++i) {
927         std::shared_ptr<AbstractEffectItem> item = std::static_pointer_cast<AbstractEffectItem>(rootItem->child(i));
928         item->setEffectStackEnabled(enabled);
929         indexes << getIndexFromItem(item);
930     }
931     if (indexes.isEmpty()) {
932         return;
933     }
934     pCore->refreshProjectItem(m_ownerId);
935     pCore->invalidateItem(m_ownerId);
936     emit dataChanged(indexes.first(), indexes.last(), {TimelineModel::EffectsEnabledRole});
937     emit enabledStateChanged();
938 }
939 
getEffectStackRow(int row,const std::shared_ptr<TreeItem> & parentItem)940 std::shared_ptr<AbstractEffectItem> EffectStackModel::getEffectStackRow(int row, const std::shared_ptr<TreeItem> &parentItem)
941 {
942     return std::static_pointer_cast<AbstractEffectItem>(parentItem ? parentItem->child(row) : rootItem->child(row));
943 }
944 
importEffects(const std::shared_ptr<EffectStackModel> & sourceStack,PlaylistState::ClipState state)945 bool EffectStackModel::importEffects(const std::shared_ptr<EffectStackModel> &sourceStack, PlaylistState::ClipState state)
946 {
947     QWriteLocker locker(&m_lock);
948     // TODO: manage fades, keyframes if clips don't have same size / in point
949     bool found = false;
950     bool effectEnabled = rootItem->childCount() > 0;
951     int imported = 0;
952     for (int i = 0; i < sourceStack->rowCount(); i++) {
953         auto item = sourceStack->getEffectStackRow(i);
954         // NO undo. this should only be used on project opening
955         if (copyEffect(item, state, false)) {
956             found = true;
957             if (item->isEnabled()) {
958                 effectEnabled = true;
959             }
960             imported++;
961         }
962     }
963     if (!effectEnabled && imported == 0) {
964         effectEnabled = true;
965     }
966     m_effectStackEnabled = effectEnabled;
967     if (!m_effectStackEnabled) {
968         // Mark all effects as disabled by stack
969         for (int i = 0; i < rootItem->childCount(); ++i) {
970             std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
971             sourceEffect->setEffectStackEnabled(false);
972             sourceEffect->setEnabled(true);
973         }
974     }
975     if (found) {
976         emit modelChanged();
977     }
978     return found;
979 }
980 
importEffects(const std::weak_ptr<Mlt::Service> & service,PlaylistState::ClipState state,bool alreadyExist,QString originalDecimalPoint)981 void EffectStackModel::importEffects(const std::weak_ptr<Mlt::Service> &service, PlaylistState::ClipState state, bool alreadyExist, QString originalDecimalPoint)
982 {
983     QWriteLocker locker(&m_lock);
984     m_loadingExisting = alreadyExist;
985     bool effectEnabled = true;
986     if (auto ptr = service.lock()) {
987         int max = ptr->filter_count();
988         int imported = 0;
989         for (int i = 0; i < max; i++) {
990             std::unique_ptr<Mlt::Filter> filter(ptr->filter(i));
991             if (filter->get_int("internal_added") > 0 && m_ownerId.first != ObjectType::TimelineTrack) {
992                 // Required to load master audio effects
993                 if (m_ownerId.first == ObjectType::Master && filter->get("mlt_service") == QLatin1String("avfilter.subtitles")) {
994                     // A subtitle filter, update project
995                     QMap<QString, QString> subProperties;
996                     //subProperties.insert(QStringLiteral("av.filename"), filter->get("av.filename"));
997                     subProperties.insert(QStringLiteral("disable"), filter->get("disable"));
998                     subProperties.insert(QStringLiteral("kdenlive:locked"), filter->get("kdenlive:locked"));
999                     pCore->window()->slotEditSubtitle(subProperties);
1000                 } else if (auto ms = m_masterService.lock()) {
1001                     ms->attach(*filter.get());
1002                 }
1003                 continue;
1004             }
1005             if (filter->get("kdenlive_id") == nullptr) {
1006                 // don't consider internal MLT stuff
1007                 continue;
1008             }
1009             const QString effectId = qstrdup(filter->get("kdenlive_id"));
1010             if (m_ownerId.first == ObjectType::TimelineClip && EffectsRepository::get()->isUnique(effectId) && hasEffect(effectId))  {
1011                 pCore->displayMessage(i18n("Effect %1 cannot be added twice.", EffectsRepository::get()->getName(effectId)), ErrorMessage);
1012                 continue;
1013             }
1014             if (filter->get_int("disable") == 0) {
1015                 effectEnabled = true;
1016             }
1017             // The MLT filter already exists, use it directly to create the effect
1018             std::shared_ptr<EffectItemModel> effect;
1019             if (alreadyExist) {
1020                 // effect is already plugged in the service
1021                 effect = EffectItemModel::construct(std::move(filter), shared_from_this(), originalDecimalPoint);
1022             } else {
1023                 // duplicate effect
1024                 std::unique_ptr<Mlt::Filter> asset = EffectsRepository::get()->getEffect(effectId);
1025                 asset->inherit(*(filter));
1026                 effect = EffectItemModel::construct(std::move(asset), shared_from_this(), originalDecimalPoint);
1027             }
1028             if (effect->isAudio()) {
1029                 if (state == PlaylistState::VideoOnly) {
1030                     // Don't import effect
1031                     continue;
1032                 }
1033             } else if (state == PlaylistState::AudioOnly) {
1034                 // Don't import effect
1035                 continue;
1036             }
1037             imported++;
1038             connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
1039             connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
1040             connect(effect.get(), &AssetParameterModel::showEffectZone, this, &EffectStackModel::updateEffectZones);
1041             Fun redo = addItem_lambda(effect, rootItem->getId());
1042             effect->prepareKeyframes();
1043             if (redo()) {
1044                 if (effectId.startsWith(QLatin1String("fadein")) || effectId.startsWith(QLatin1String("fade_from_"))) {
1045                     m_fadeIns.insert(effect->getId());
1046                     int clipIn = ptr->get_int("in");
1047                     if (effect->filter().get_int("in") != clipIn) {
1048                         // Broken fade, fix
1049                         int filterLength = effect->filter().get_length() - 1;
1050                         effect->filter().set("in", clipIn);
1051                         effect->filter().set("out", clipIn + filterLength);
1052                     }
1053                 } else if (effectId.startsWith(QLatin1String("fadeout")) || effectId.startsWith(QLatin1String("fade_to_"))) {
1054                     m_fadeOuts.insert(effect->getId());
1055                     int clipOut = ptr->get_int("out");
1056                     if (effect->filter().get_int("out") != clipOut) {
1057                         // Broken fade, fix
1058                         int filterLength = effect->filter().get_length() - 1;
1059                         effect->filter().set("in", clipOut - filterLength);
1060                         effect->filter().set("out", clipOut);
1061                     }
1062                 }
1063             }
1064         }
1065         if (imported == 0) {
1066             effectEnabled = true;
1067         }
1068     }
1069     m_effectStackEnabled = effectEnabled;
1070     if (!m_effectStackEnabled) {
1071         // Mark all effects as disabled by stack
1072         for (int i = 0; i < rootItem->childCount(); ++i) {
1073             std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
1074             sourceEffect->setEffectStackEnabled(false);
1075             sourceEffect->setEnabled(true);
1076         }
1077     }
1078     m_loadingExisting = false;
1079     emit modelChanged();
1080 }
1081 
setActiveEffect(int ix)1082 void EffectStackModel::setActiveEffect(int ix)
1083 {
1084     QWriteLocker locker(&m_lock);
1085     int current = -1;
1086     if (auto ptr = m_masterService.lock()) {
1087         current = ptr->get_int("kdenlive:activeeffect");
1088         ptr->set("kdenlive:activeeffect", ix);
1089     }
1090     // Desactivate previous effect
1091     if (current > -1 && current != ix && current < rootItem->childCount()) {
1092         std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(current));
1093         if (effect) {
1094             effect->setActive(false);
1095             emit currentChanged(getIndexFromItem(effect), false);
1096         }
1097     }
1098     // Activate new effect
1099     if (ix > -1 && ix < rootItem->childCount()) {
1100         std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix));
1101         if (effect) {
1102             effect->setActive(true);
1103             emit currentChanged(getIndexFromItem(effect), true);
1104         }
1105     }
1106     pCore->updateItemKeyframes(m_ownerId);
1107 }
1108 
getActiveEffect() const1109 int EffectStackModel::getActiveEffect() const
1110 {
1111     QWriteLocker locker(&m_lock);
1112     if (auto ptr = m_masterService.lock()) {
1113         return ptr->get_int("kdenlive:activeeffect");
1114     }
1115     return 0;
1116 }
1117 
slotCreateGroup(const std::shared_ptr<EffectItemModel> & childEffect)1118 void EffectStackModel::slotCreateGroup(const std::shared_ptr<EffectItemModel> &childEffect)
1119 {
1120     QWriteLocker locker(&m_lock);
1121     auto groupItem = EffectGroupModel::construct(QStringLiteral("group"), shared_from_this());
1122     rootItem->appendChild(groupItem);
1123     groupItem->appendChild(childEffect);
1124 }
1125 
getOwnerId() const1126 ObjectId EffectStackModel::getOwnerId() const
1127 {
1128     return m_ownerId;
1129 }
1130 
checkConsistency()1131 bool EffectStackModel::checkConsistency()
1132 {
1133     if (!AbstractTreeModel::checkConsistency()) {
1134         return false;
1135     }
1136 
1137     std::vector<std::shared_ptr<EffectItemModel>> allFilters;
1138     // We do a DFS on the tree to retrieve all the filters
1139     std::stack<std::shared_ptr<AbstractEffectItem>> stck;
1140     stck.push(std::static_pointer_cast<AbstractEffectItem>(rootItem));
1141 
1142     while (!stck.empty()) {
1143         auto current = stck.top();
1144         stck.pop();
1145 
1146         if (current->effectItemType() == EffectItemType::Effect) {
1147             if (current->childCount() > 0) {
1148                 qDebug() << "ERROR: Found an effect with children";
1149                 return false;
1150             }
1151             allFilters.push_back(std::static_pointer_cast<EffectItemModel>(current));
1152             continue;
1153         }
1154         for (int i = current->childCount() - 1; i >= 0; --i) {
1155             stck.push(std::static_pointer_cast<AbstractEffectItem>(current->child(i)));
1156         }
1157     }
1158 
1159     for (const auto &service : m_childServices) {
1160         auto ptr = service.lock();
1161         if (!ptr) {
1162             qDebug() << "ERROR: unavailable service";
1163             return false;
1164         }
1165         // MLT inserts some default normalizer filters that are not managed by Kdenlive, which explains  why the filter count is not equal
1166         int kdenliveFilterCount = 0;
1167         for (int i = 0; i < ptr->filter_count(); i++) {
1168             std::shared_ptr<Mlt::Filter> filt(ptr->filter(i));
1169             if (filt->get("kdenlive_id") != nullptr) {
1170                 kdenliveFilterCount++;
1171             }
1172             // qDebug() << "FILTER: "<<i<<" : "<<ptr->filter(i)->get("mlt_service");
1173         }
1174         if (kdenliveFilterCount != int(allFilters.size())) {
1175             qDebug() << "ERROR: Wrong filter count: " << kdenliveFilterCount << " = " << allFilters.size();
1176             return false;
1177         }
1178 
1179         int ct = 0;
1180         for (uint i = 0; i < allFilters.size(); ++i) {
1181             while (ptr->filter(ct)->get("kdenlive_id") == nullptr && ct < ptr->filter_count()) {
1182                 ct++;
1183             }
1184             auto mltFilter = ptr->filter(ct);
1185             auto currentFilter = allFilters[i]->filter();
1186             if (QString(currentFilter.get("mlt_service")) != QLatin1String(mltFilter->get("mlt_service"))) {
1187                 qDebug() << "ERROR: filter " << i << "differ: " << ct << ", " << currentFilter.get("mlt_service") << " = " << mltFilter->get("mlt_service");
1188                 return false;
1189             }
1190             QVector<QPair<QString, QVariant>> params = allFilters[i]->getAllParameters();
1191             for (const auto &val : qAsConst(params)) {
1192                 // Check parameters values
1193                 if (val.second != QVariant(mltFilter->get(val.first.toUtf8().constData()))) {
1194                     qDebug() << "ERROR: filter " << i << "PARAMETER MISMATCH: " << val.first << " = " << val.second
1195                              << " != " << mltFilter->get(val.first.toUtf8().constData());
1196                     return false;
1197                 }
1198             }
1199             ct++;
1200         }
1201     }
1202 
1203     return true;
1204 }
1205 
adjust(const QString & effectId,const QString & effectName,double value)1206 void EffectStackModel::adjust(const QString &effectId, const QString &effectName, double value)
1207 {
1208     QWriteLocker locker(&m_lock);
1209     for (int i = 0; i < rootItem->childCount(); ++i) {
1210         std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
1211         if (effectId == sourceEffect->getAssetId()) {
1212             sourceEffect->setParameter(effectName, QString::number(value));
1213             return;
1214         }
1215     }
1216 }
1217 
getAssetModelById(const QString & effectId)1218 std::shared_ptr<AssetParameterModel> EffectStackModel::getAssetModelById(const QString &effectId)
1219 {
1220     QWriteLocker locker(&m_lock);
1221     for (int i = 0; i < rootItem->childCount(); ++i) {
1222         std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
1223         if (effectId == sourceEffect->getAssetId()) {
1224             return std::static_pointer_cast<AssetParameterModel>(sourceEffect);
1225         }
1226     }
1227     return nullptr;
1228 }
1229 
1230 
hasFilter(const QString & effectId) const1231 bool EffectStackModel::hasFilter(const QString &effectId) const
1232 {
1233     READ_LOCK();
1234     return rootItem->accumulate_const(false, [effectId](bool b, std::shared_ptr<const TreeItem> it) {
1235         if (b) return true;
1236         auto item = std::static_pointer_cast<const AbstractEffectItem>(it);
1237         if (item->effectItemType() == EffectItemType::Group) {
1238             return false;
1239         }
1240         auto sourceEffect = std::static_pointer_cast<const EffectItemModel>(it);
1241         return effectId == sourceEffect->getAssetId();
1242     });
1243 }
1244 
getFilterParam(const QString & effectId,const QString & paramName)1245 double EffectStackModel::getFilterParam(const QString &effectId, const QString &paramName)
1246 {
1247     READ_LOCK();
1248     for (int i = 0; i < rootItem->childCount(); ++i) {
1249         std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
1250         if (effectId == sourceEffect->getAssetId()) {
1251             return sourceEffect->filter().get_double(paramName.toUtf8().constData());
1252         }
1253     }
1254     return 0.0;
1255 }
1256 
getEffectKeyframeModel()1257 KeyframeModel *EffectStackModel::getEffectKeyframeModel()
1258 {
1259     if (rootItem->childCount() == 0) return nullptr;
1260     int ix = getActiveEffect();
1261     if (ix < 0 || ix >= rootItem->childCount()) {
1262         return nullptr;
1263     }
1264     std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix));
1265     std::shared_ptr<KeyframeModelList> listModel = sourceEffect->getKeyframeModel();
1266     if (listModel) {
1267         return listModel->getKeyModel();
1268     }
1269     return nullptr;
1270 }
1271 
replugEffect(const std::shared_ptr<AssetParameterModel> & asset)1272 void EffectStackModel::replugEffect(const std::shared_ptr<AssetParameterModel> &asset)
1273 {
1274     QWriteLocker locker(&m_lock);
1275     auto effectItem = std::static_pointer_cast<EffectItemModel>(asset);
1276     int oldRow = effectItem->row();
1277     int count = rowCount();
1278     for (int ix = oldRow; ix < count; ix++) {
1279         auto item = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix));
1280         item->unplant(m_masterService);
1281         for (const auto &service : m_childServices) {
1282             item->unplantClone(service);
1283         }
1284     }
1285     std::unique_ptr<Mlt::Properties> effect = EffectsRepository::get()->getEffect(effectItem->getAssetId());
1286     effect->inherit(effectItem->filter());
1287     effectItem->resetAsset(std::move(effect));
1288     for (int ix = oldRow; ix < count; ix++) {
1289         auto item = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix));
1290         item->plant(m_masterService);
1291         for (const auto &service : m_childServices) {
1292             item->plantClone(service);
1293         }
1294     }
1295 }
1296 
cleanFadeEffects(bool outEffects,Fun & undo,Fun & redo)1297 void EffectStackModel::cleanFadeEffects(bool outEffects, Fun &undo, Fun &redo)
1298 {
1299     QWriteLocker locker(&m_lock);
1300     const auto &toDelete = outEffects ? m_fadeOuts : m_fadeIns;
1301     for (int id : toDelete) {
1302         auto effect = std::static_pointer_cast<EffectItemModel>(getItemById(id));
1303         Fun operation = removeItem_lambda(id);
1304         if (operation()) {
1305             Fun reverse = addItem_lambda(effect, rootItem->getId());
1306             UPDATE_UNDO_REDO(operation, reverse, undo, redo);
1307         }
1308     }
1309     if (!toDelete.empty()) {
1310         Fun updateRedo = [this, toDelete, outEffects]() {
1311             for (int id : toDelete) {
1312                 if (outEffects) {
1313                     m_fadeOuts.erase(id);
1314                 } else {
1315                     m_fadeIns.erase(id);
1316                 }
1317             }
1318             QVector<int> roles = {TimelineModel::EffectNamesRole};
1319             roles << (outEffects ? TimelineModel::FadeOutRole : TimelineModel::FadeInRole);
1320             emit dataChanged(QModelIndex(), QModelIndex(), roles);
1321             pCore->updateItemKeyframes(m_ownerId);
1322             return true;
1323         };
1324         updateRedo();
1325         PUSH_LAMBDA(updateRedo, redo);
1326     }
1327 }
1328 
effectNames() const1329 const QString EffectStackModel::effectNames() const
1330 {
1331     QStringList effects;
1332     for (int i = 0; i < rootItem->childCount(); ++i) {
1333         effects.append(EffectsRepository::get()->getName(std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->getAssetId()));
1334     }
1335     return effects.join(QLatin1Char('/'));
1336 }
1337 
externalFiles() const1338 QStringList EffectStackModel::externalFiles() const
1339 {
1340     QStringList urls;
1341     for (int i = 0; i < rootItem->childCount(); ++i) {
1342         auto filter = std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->filter();
1343         QString url = filter.get("av.file");
1344         if(url.isEmpty()) {
1345             url = filter.get("luma.resource");
1346         }
1347         if(!url.isEmpty()) {
1348             urls << url;
1349         }
1350         urls << url;
1351     }
1352     return urls;
1353 }
1354 
isStackEnabled() const1355 bool EffectStackModel::isStackEnabled() const
1356 {
1357     return m_effectStackEnabled;
1358 }
1359 
addEffectKeyFrame(int frame,double normalisedVal)1360 bool EffectStackModel::addEffectKeyFrame(int frame, double normalisedVal)
1361 {
1362     if (rootItem->childCount() == 0) return false;
1363     int ix = getActiveEffect();
1364     if (ix < 0) {
1365         return false;
1366     }
1367     std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix));
1368     std::shared_ptr<KeyframeModelList> listModel = sourceEffect->getKeyframeModel();
1369     if (m_ownerId.first == ObjectType::TimelineTrack) {
1370         sourceEffect->filter().set("out", pCore->getItemDuration(m_ownerId));
1371     }
1372     return listModel->addKeyframe(frame, normalisedVal);
1373 }
1374 
removeKeyFrame(int frame)1375 bool EffectStackModel::removeKeyFrame(int frame)
1376 {
1377     if (rootItem->childCount() == 0) return false;
1378     int ix = getActiveEffect();
1379     if (ix < 0) {
1380         return false;
1381     }
1382     std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix));
1383     std::shared_ptr<KeyframeModelList> listModel = sourceEffect->getKeyframeModel();
1384     return listModel->removeKeyframe(GenTime(frame, pCore->getCurrentFps()));
1385 }
1386 
updateKeyFrame(int oldFrame,int newFrame,QVariant normalisedVal)1387 bool EffectStackModel::updateKeyFrame(int oldFrame, int newFrame, QVariant normalisedVal)
1388 {
1389     if (rootItem->childCount() == 0) return false;
1390     int ix = getActiveEffect();
1391     if (ix < 0) {
1392         return false;
1393     }
1394     std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix));
1395     std::shared_ptr<KeyframeModelList> listModel = sourceEffect->getKeyframeModel();
1396     if (m_ownerId.first == ObjectType::TimelineTrack) {
1397         sourceEffect->filter().set("out", pCore->getItemDuration(m_ownerId));
1398     }
1399     return listModel->updateKeyframe(GenTime(oldFrame, pCore->getCurrentFps()), GenTime(newFrame, pCore->getCurrentFps()), std::move(normalisedVal));
1400 }
1401 
hasKeyFrame(int frame)1402 bool EffectStackModel::hasKeyFrame(int frame)
1403 {
1404     if (rootItem->childCount() == 0) return false;
1405     int ix = getActiveEffect();
1406     if (ix < 0) {
1407         return false;
1408     }
1409     std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix));
1410     std::shared_ptr<KeyframeModelList> listModel = sourceEffect->getKeyframeModel();
1411     return listModel->hasKeyframe(frame);
1412 }
1413 
hasEffect(const QString & assetId) const1414 bool EffectStackModel::hasEffect(const QString &assetId) const
1415 {
1416     for (int i = 0; i < rootItem->childCount(); ++i) {
1417         if (std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->getAssetId() == assetId) {
1418             return true;
1419         }
1420     }
1421     return false;
1422 }
1423 
getEffectZones() const1424 QVariantList EffectStackModel::getEffectZones() const
1425 {
1426     QVariantList effectZones;
1427     for (int i = 0; i < rootItem->childCount(); ++i) {
1428         auto item = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
1429         if (item->hasForcedInOut()) {
1430             QPair<int, int> z = item->getInOut();
1431             effectZones << QPoint(z.first, z.second);
1432         }
1433     }
1434     return effectZones;
1435 }
1436 
updateEffectZones()1437 void EffectStackModel::updateEffectZones()
1438 {
1439     emit dataChanged(QModelIndex(), QModelIndex(), {TimelineModel::EffectZonesRole});
1440     if (m_ownerId.first == ObjectType::Master) {
1441         emit updateMasterZones();
1442     }
1443 }
1444