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 ¶m : 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 ¶m : 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 ¶m : 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 ¶m : 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 ¶mName)
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