File indexing completed on 2024-05-19 04:54:24
0001 /* 0002 SPDX-FileCopyrightText: 2017 Nicolas Carion 0003 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0004 */ 0005 #include "effectstackmodel.hpp" 0006 #include "assets/keyframes/model/keyframemodellist.hpp" 0007 #include "core.h" 0008 #include "doc/docundostack.hpp" 0009 #include "effectgroupmodel.hpp" 0010 #include "effectitemmodel.hpp" 0011 #include "effects/effectsrepository.hpp" 0012 #include "macros.hpp" 0013 #include "mainwindow.h" 0014 #include "timeline2/model/timelinemodel.hpp" 0015 #include <profiles/profilemodel.hpp> 0016 #include <stack> 0017 #include <utility> 0018 #include <vector> 0019 0020 EffectStackModel::EffectStackModel(std::weak_ptr<Mlt::Service> service, ObjectId ownerId, std::weak_ptr<DocUndoStack> undo_stack) 0021 : AbstractTreeModel() 0022 , m_masterService(std::move(service)) 0023 , m_effectStackEnabled(true) 0024 , m_ownerId(std::move(ownerId)) 0025 , m_undoStack(std::move(undo_stack)) 0026 , m_lock(QReadWriteLock::Recursive) 0027 , m_loadingExisting(false) 0028 { 0029 } 0030 0031 std::shared_ptr<EffectStackModel> EffectStackModel::construct(std::weak_ptr<Mlt::Service> service, ObjectId ownerId, std::weak_ptr<DocUndoStack> undo_stack) 0032 { 0033 std::shared_ptr<EffectStackModel> self(new EffectStackModel(std::move(service), ownerId, std::move(undo_stack))); 0034 self->rootItem = EffectGroupModel::construct(QStringLiteral("root"), self, true); 0035 return self; 0036 } 0037 0038 void EffectStackModel::resetService(std::weak_ptr<Mlt::Service> service) 0039 { 0040 QWriteLocker locker(&m_lock); 0041 m_masterService = std::move(service); 0042 m_childServices.clear(); 0043 // replant all effects in new service 0044 for (int i = 0; i < rootItem->childCount(); ++i) { 0045 std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->plant(m_masterService); 0046 } 0047 } 0048 0049 void EffectStackModel::addService(std::weak_ptr<Mlt::Service> service) 0050 { 0051 QWriteLocker locker(&m_lock); 0052 m_childServices.emplace_back(std::move(service)); 0053 for (int i = 0; i < rootItem->childCount(); ++i) { 0054 std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->plantClone(m_childServices.back()); 0055 } 0056 } 0057 0058 void EffectStackModel::loadService(std::weak_ptr<Mlt::Service> service) 0059 { 0060 QWriteLocker locker(&m_lock); 0061 m_childServices.emplace_back(std::move(service)); 0062 for (int i = 0; i < rootItem->childCount(); ++i) { 0063 std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->loadClone(m_childServices.back()); 0064 } 0065 } 0066 0067 void EffectStackModel::removeService(const std::shared_ptr<Mlt::Service> &service) 0068 { 0069 QWriteLocker locker(&m_lock); 0070 std::vector<int> to_delete; 0071 for (int i = int(m_childServices.size()) - 1; i >= 0; --i) { 0072 auto ptr = m_childServices[uint(i)].lock(); 0073 if (ptr && service->get_int("_childid") == ptr->get_int("_childid")) { 0074 for (int j = 0; j < rootItem->childCount(); ++j) { 0075 std::static_pointer_cast<EffectItemModel>(rootItem->child(j))->unplantClone(ptr); 0076 } 0077 to_delete.push_back(i); 0078 } 0079 } 0080 for (int i : to_delete) { 0081 m_childServices.erase(m_childServices.begin() + i); 0082 } 0083 } 0084 0085 void EffectStackModel::removeCurrentEffect() 0086 { 0087 int ix = getActiveEffect(); 0088 if (ix < 0 || ix >= rootItem->childCount()) { 0089 return; 0090 } 0091 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix)); 0092 if (effect) { 0093 removeEffect(effect); 0094 } 0095 } 0096 0097 void EffectStackModel::removeAllEffects(Fun &undo, Fun &redo) 0098 { 0099 QWriteLocker locker(&m_lock); 0100 int current = getActiveEffect(); 0101 while (rootItem->childCount() > 0) { 0102 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(0)); 0103 int parentId = -1; 0104 if (auto ptr = effect->parentItem().lock()) parentId = ptr->getId(); 0105 Fun local_undo = addItem_lambda(effect, parentId); 0106 Fun local_redo = removeItem_lambda(effect->getId()); 0107 local_redo(); 0108 UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); 0109 } 0110 std::unordered_set<int> fadeIns = m_fadeIns; 0111 std::unordered_set<int> fadeOuts = m_fadeOuts; 0112 Fun undo_current = [this, current, fadeIns, fadeOuts]() { 0113 if (auto srv = m_masterService.lock()) { 0114 srv->set("kdenlive:activeeffect", current); 0115 } 0116 m_fadeIns = fadeIns; 0117 m_fadeOuts = fadeOuts; 0118 QVector<int> roles = {TimelineModel::EffectNamesRole}; 0119 if (!m_fadeIns.empty()) { 0120 roles << TimelineModel::FadeInRole; 0121 } 0122 if (!m_fadeOuts.empty()) { 0123 roles << TimelineModel::FadeOutRole; 0124 } 0125 Q_EMIT dataChanged(QModelIndex(), QModelIndex(), roles); 0126 pCore->updateItemKeyframes(m_ownerId); 0127 return true; 0128 }; 0129 Fun redo_current = [this]() { 0130 if (auto srv = m_masterService.lock()) { 0131 srv->set("kdenlive:activeeffect", -1); 0132 } 0133 QVector<int> roles = {TimelineModel::EffectNamesRole}; 0134 if (!m_fadeIns.empty()) { 0135 roles << TimelineModel::FadeInRole; 0136 } 0137 if (!m_fadeOuts.empty()) { 0138 roles << TimelineModel::FadeOutRole; 0139 } 0140 m_fadeIns.clear(); 0141 m_fadeOuts.clear(); 0142 Q_EMIT dataChanged(QModelIndex(), QModelIndex(), roles); 0143 pCore->updateItemKeyframes(m_ownerId); 0144 return true; 0145 }; 0146 redo_current(); 0147 PUSH_LAMBDA(redo_current, redo); 0148 PUSH_LAMBDA(undo_current, undo); 0149 } 0150 0151 void EffectStackModel::removeEffect(const std::shared_ptr<EffectItemModel> &effect) 0152 { 0153 qDebug() << "* * ** REMOVING EFFECT FROM STACK!!!\n!!!!!!!!!"; 0154 QWriteLocker locker(&m_lock); 0155 Q_ASSERT(m_allItems.count(effect->getId()) > 0); 0156 int parentId = -1; 0157 if (auto ptr = effect->parentItem().lock()) parentId = ptr->getId(); 0158 int current = getActiveEffect(); 0159 if (current >= rootItem->childCount() - 1) { 0160 setActiveEffect(current - 1); 0161 } 0162 int currentRow = effect->row(); 0163 Fun undo = addItem_lambda(effect, parentId); 0164 if (currentRow != rowCount() - 1) { 0165 Fun move = moveItem_lambda(effect->getId(), currentRow, true); 0166 PUSH_LAMBDA(move, undo); 0167 } 0168 Fun redo = removeItem_lambda(effect->getId()); 0169 bool res = redo(); 0170 if (res) { 0171 int inFades = int(m_fadeIns.size()); 0172 int outFades = int(m_fadeOuts.size()); 0173 m_fadeIns.erase(effect->getId()); 0174 m_fadeOuts.erase(effect->getId()); 0175 inFades = int(m_fadeIns.size()) - inFades; 0176 outFades = int(m_fadeOuts.size()) - outFades; 0177 QString effectName = EffectsRepository::get()->getName(effect->getAssetId()); 0178 Fun update = [this, inFades, outFades]() { 0179 // Required to build the effect view 0180 if (rowCount() == 0) { 0181 // Stack is now empty 0182 Q_EMIT dataChanged(QModelIndex(), QModelIndex(), {}); 0183 } else { 0184 QVector<int> roles = {TimelineModel::EffectNamesRole}; 0185 if (inFades < 0) { 0186 roles << TimelineModel::FadeInRole; 0187 } 0188 if (outFades < 0) { 0189 roles << TimelineModel::FadeOutRole; 0190 } 0191 Q_EMIT dataChanged(QModelIndex(), QModelIndex(), roles); 0192 } 0193 // TODO: only update if effect is fade or keyframe 0194 /*if (inFades < 0) { 0195 pCore->updateItemModel(m_ownerId, QStringLiteral("fadein")); 0196 } else if (outFades < 0) { 0197 pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout")); 0198 }*/ 0199 updateEffectZones(); 0200 pCore->updateItemKeyframes(m_ownerId); 0201 return true; 0202 }; 0203 Fun update2 = [this, inFades, outFades, current]() { 0204 // Required to build the effect view 0205 QVector<int> roles = {TimelineModel::EffectNamesRole}; 0206 // TODO: only update if effect is fade or keyframe 0207 if (inFades < 0) { 0208 roles << TimelineModel::FadeInRole; 0209 } else if (outFades < 0) { 0210 roles << TimelineModel::FadeOutRole; 0211 } 0212 Q_EMIT dataChanged(QModelIndex(), QModelIndex(), roles); 0213 updateEffectZones(); 0214 pCore->updateItemKeyframes(m_ownerId); 0215 setActiveEffect(current); 0216 return true; 0217 }; 0218 update(); 0219 PUSH_LAMBDA(update, redo); 0220 PUSH_LAMBDA(update2, undo); 0221 PUSH_UNDO(undo, redo, i18n("Delete effect %1", effectName)); 0222 } else { 0223 qDebug() << "..........FAILED EFFECT DELETION"; 0224 } 0225 } 0226 0227 bool EffectStackModel::copyXmlEffect(const QDomElement &effect) 0228 { 0229 std::function<bool(void)> undo = []() { return true; }; 0230 std::function<bool(void)> redo = []() { return true; }; 0231 bool result = fromXml(effect, undo, redo); 0232 if (result) { 0233 PUSH_UNDO(undo, redo, i18n("Copy effect")); 0234 } 0235 return result; 0236 } 0237 0238 bool EffectStackModel::copyXmlEffectWithUndo(const QDomElement &effect, Fun &undo, Fun &redo) 0239 { 0240 bool result = fromXml(effect, undo, redo); 0241 return result; 0242 } 0243 0244 QDomElement EffectStackModel::toXml(QDomDocument &document) 0245 { 0246 QDomElement container = document.createElement(QStringLiteral("effects")); 0247 int currentIn = pCore->getItemIn(m_ownerId); 0248 container.setAttribute(QStringLiteral("parentIn"), currentIn); 0249 for (int i = 0; i < rootItem->childCount(); ++i) { 0250 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 0251 QDomElement sub = document.createElement(QStringLiteral("effect")); 0252 sub.setAttribute(QStringLiteral("id"), sourceEffect->getAssetId()); 0253 int filterIn = sourceEffect->filter().get_int("in"); 0254 int filterOut = sourceEffect->filter().get_int("out"); 0255 if (filterOut > filterIn) { 0256 sub.setAttribute(QStringLiteral("in"), filterIn); 0257 sub.setAttribute(QStringLiteral("out"), filterOut); 0258 } 0259 QStringList passProps{QStringLiteral("disable"), QStringLiteral("kdenlive:collapsed")}; 0260 for (const QString ¶m : passProps) { 0261 int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData()); 0262 if (paramVal > 0) { 0263 Xml::setXmlProperty(sub, param, QString::number(paramVal)); 0264 } 0265 } 0266 QVector<QPair<QString, QVariant>> params = sourceEffect->getAllParameters(); 0267 for (const auto ¶m : qAsConst(params)) { 0268 Xml::setXmlProperty(sub, param.first, param.second.toString()); 0269 } 0270 container.appendChild(sub); 0271 } 0272 return container; 0273 } 0274 0275 QDomElement EffectStackModel::rowToXml(const QUuid &uuid, int row, QDomDocument &document) 0276 { 0277 QDomElement container = document.createElement(QStringLiteral("effects")); 0278 if (row < 0 || row >= rootItem->childCount()) { 0279 return container; 0280 } 0281 int currentIn = pCore->getItemIn(uuid, m_ownerId); 0282 container.setAttribute(QStringLiteral("parentIn"), currentIn); 0283 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(row)); 0284 QDomElement sub = document.createElement(QStringLiteral("effect")); 0285 sub.setAttribute(QStringLiteral("id"), sourceEffect->getAssetId()); 0286 int filterIn = sourceEffect->filter().get_int("in"); 0287 int filterOut = sourceEffect->filter().get_int("out"); 0288 if (filterOut > filterIn) { 0289 sub.setAttribute(QStringLiteral("in"), filterIn); 0290 sub.setAttribute(QStringLiteral("out"), filterOut); 0291 } 0292 QStringList passProps{QStringLiteral("disable"), QStringLiteral("kdenlive:collapsed")}; 0293 for (const QString ¶m : passProps) { 0294 int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData()); 0295 if (paramVal > 0) { 0296 Xml::setXmlProperty(sub, param, QString::number(paramVal)); 0297 } 0298 } 0299 QVector<QPair<QString, QVariant>> params = sourceEffect->getAllParameters(); 0300 for (const auto ¶m : qAsConst(params)) { 0301 Xml::setXmlProperty(sub, param.first, param.second.toString()); 0302 } 0303 container.appendChild(sub); 0304 return container; 0305 } 0306 0307 bool EffectStackModel::fromXml(const QDomElement &effectsXml, Fun &undo, Fun &redo) 0308 { 0309 QDomNodeList nodeList = effectsXml.elementsByTagName(QStringLiteral("effect")); 0310 int parentIn = effectsXml.attribute(QStringLiteral("parentIn")).toInt(); 0311 qDebug() << "// GOT PREVIOUS PARENTIN: " << parentIn << "\n\n=======\n=======\n\n"; 0312 int currentIn = pCore->getItemIn(m_ownerId); 0313 PlaylistState::ClipState state = pCore->getItemState(m_ownerId); 0314 bool effectAdded = false; 0315 for (int i = 0; i < nodeList.count(); ++i) { 0316 QDomElement node = nodeList.item(i).toElement(); 0317 const QString effectId = node.attribute(QStringLiteral("id")); 0318 bool isAudioEffect = EffectsRepository::get()->isAudioEffect(effectId); 0319 if (state == PlaylistState::VideoOnly) { 0320 if (isAudioEffect) { 0321 continue; 0322 } 0323 } else if (state == PlaylistState::AudioOnly) { 0324 if (!isAudioEffect) { 0325 continue; 0326 } 0327 } 0328 if (m_ownerId.type == KdenliveObjectType::TimelineClip && EffectsRepository::get()->isUnique(effectId) && hasEffect(effectId)) { 0329 pCore->displayMessage(i18n("Effect %1 cannot be added twice.", EffectsRepository::get()->getName(effectId)), ErrorMessage); 0330 return false; 0331 } 0332 bool effectEnabled = true; 0333 if (Xml::hasXmlProperty(node, QLatin1String("disable"))) { 0334 effectEnabled = Xml::getXmlProperty(node, QLatin1String("disable")).toInt() != 1; 0335 } 0336 auto effect = EffectItemModel::construct(effectId, shared_from_this(), effectEnabled); 0337 const QString in = node.attribute(QStringLiteral("in")); 0338 const QString out = node.attribute(QStringLiteral("out")); 0339 if (!out.isEmpty()) { 0340 effect->filter().set("in", in.toUtf8().constData()); 0341 effect->filter().set("out", out.toUtf8().constData()); 0342 } 0343 QMap<QString, std::pair<ParamType, bool>> keyframeParams = effect->getKeyframableParameters(); 0344 QVector<QPair<QString, QVariant>> parameters; 0345 QDomNodeList params = node.elementsByTagName(QStringLiteral("property")); 0346 for (int j = 0; j < params.count(); j++) { 0347 QDomElement pnode = params.item(j).toElement(); 0348 const QString pName = pnode.attribute(QStringLiteral("name")); 0349 if (pName == QLatin1String("in") || pName == QLatin1String("out")) { 0350 continue; 0351 } 0352 if (keyframeParams.contains(pName)) { 0353 // This is a keyframable parameter, fix offset 0354 int currentDuration = pCore->getItemDuration(m_ownerId); 0355 if (currentDuration > 1) { 0356 currentDuration--; 0357 currentDuration += currentIn; 0358 } 0359 QString pValue = KeyframeModel::getAnimationStringWithOffset(effect, pnode.text(), currentIn - parentIn, currentDuration, 0360 keyframeParams.value(pName).first, keyframeParams.value(pName).second); 0361 parameters.append(QPair<QString, QVariant>(pName, QVariant(pValue))); 0362 } else { 0363 parameters.append(QPair<QString, QVariant>(pName, QVariant(pnode.text()))); 0364 } 0365 } 0366 effect->setParameters(parameters); 0367 Fun local_undo = removeItem_lambda(effect->getId()); 0368 // TODO the parent should probably not always be the root 0369 Fun local_redo = addItem_lambda(effect, rootItem->getId()); 0370 effect->prepareKeyframes(); 0371 connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged); 0372 connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection); 0373 connect(effect.get(), &AssetParameterModel::showEffectZone, this, &EffectStackModel::updateEffectZones); 0374 if (effectId.startsWith(QLatin1String("fadein")) || effectId.startsWith(QLatin1String("fade_from_"))) { 0375 m_fadeIns.insert(effect->getId()); 0376 int duration = effect->filter().get_length() - 1; 0377 effect->filter().set("in", currentIn); 0378 effect->filter().set("out", currentIn + duration); 0379 if (effectId.startsWith(QLatin1String("fade_"))) { 0380 if (effect->filter().get("alpha") == QLatin1String("1")) { 0381 // Adjust level value to match filter end 0382 effect->filter().set("level", "0=0;-1=1"); 0383 } else if (effect->filter().get("level") == QLatin1String("1")) { 0384 effect->filter().set("alpha", "0=0;-1=1"); 0385 } 0386 } 0387 } else if (effectId.startsWith(QLatin1String("fadeout")) || effectId.startsWith(QLatin1String("fade_to_"))) { 0388 m_fadeOuts.insert(effect->getId()); 0389 int duration = effect->filter().get_length() - 1; 0390 int filterOut = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1; 0391 effect->filter().set("in", filterOut - duration); 0392 effect->filter().set("out", filterOut); 0393 if (effectId.startsWith(QLatin1String("fade_"))) { 0394 if (effect->filter().get("alpha") == QLatin1String("1")) { 0395 // Adjust level value to match filter end 0396 effect->filter().set("level", "0=1;-1=0"); 0397 } else if (effect->filter().get("level") == QLatin1String("1")) { 0398 effect->filter().set("alpha", "0=1;-1=0"); 0399 } 0400 } 0401 } 0402 local_redo(); 0403 effectAdded = true; 0404 UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); 0405 } 0406 if (effectAdded) { 0407 Fun update = [this]() { 0408 Q_EMIT dataChanged(QModelIndex(), QModelIndex(), {}); 0409 return true; 0410 }; 0411 update(); 0412 PUSH_LAMBDA(update, redo); 0413 PUSH_LAMBDA(update, undo); 0414 } 0415 return effectAdded; 0416 } 0417 0418 bool EffectStackModel::copyEffect(const std::shared_ptr<AbstractEffectItem> &sourceItem, PlaylistState::ClipState state, bool logUndo) 0419 { 0420 QWriteLocker locker(&m_lock); 0421 if (sourceItem->childCount() > 0) { 0422 // TODO: group 0423 return false; 0424 } 0425 bool audioEffect = sourceItem->isAudio(); 0426 if (state == PlaylistState::VideoOnly) { 0427 if (audioEffect) { 0428 // This effect cannot be used 0429 return false; 0430 } 0431 } else if (state == PlaylistState::AudioOnly) { 0432 if (!audioEffect) { 0433 return false; 0434 } 0435 } 0436 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(sourceItem); 0437 const QString effectId = sourceEffect->getAssetId(); 0438 if (m_ownerId.type == KdenliveObjectType::TimelineClip && EffectsRepository::get()->isUnique(effectId) && hasEffect(effectId)) { 0439 pCore->displayMessage(i18n("Effect %1 cannot be added twice.", EffectsRepository::get()->getName(effectId)), ErrorMessage); 0440 return false; 0441 } 0442 bool enabled = sourceEffect->isEnabled(); 0443 auto effect = EffectItemModel::construct(effectId, shared_from_this(), enabled); 0444 effect->setParameters(sourceEffect->getAllParameters()); 0445 if (!enabled) { 0446 effect->filter().set("disable", 1); 0447 } 0448 if (m_ownerId.type == KdenliveObjectType::TimelineTrack || m_ownerId.type == KdenliveObjectType::Master) { 0449 effect->filter().set("in", 0); 0450 effect->filter().set("out", pCore->getItemDuration(m_ownerId) - 1); 0451 } else { 0452 effect->filter().set("in", sourceEffect->filter().get_int("in")); 0453 effect->filter().set("out", sourceEffect->filter().get_int("out")); 0454 } 0455 Fun local_undo = removeItem_lambda(effect->getId()); 0456 // TODO the parent should probably not always be the root 0457 Fun local_redo = addItem_lambda(effect, rootItem->getId()); 0458 effect->prepareKeyframes(); 0459 connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged); 0460 connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection); 0461 connect(effect.get(), &AssetParameterModel::showEffectZone, this, &EffectStackModel::updateEffectZones); 0462 QVector<int> roles = {TimelineModel::EffectNamesRole}; 0463 if (effectId.startsWith(QLatin1String("fadein")) || effectId.startsWith(QLatin1String("fade_from_"))) { 0464 m_fadeIns.insert(effect->getId()); 0465 int duration = effect->filter().get_length() - 1; 0466 int in = pCore->getItemIn(m_ownerId); 0467 effect->filter().set("in", in); 0468 effect->filter().set("out", in + duration); 0469 roles << TimelineModel::FadeInRole; 0470 } else if (effectId.startsWith(QLatin1String("fadeout")) || effectId.startsWith(QLatin1String("fade_to_"))) { 0471 m_fadeOuts.insert(effect->getId()); 0472 int duration = effect->filter().get_length() - 1; 0473 int out = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1; 0474 effect->filter().set("in", out - duration); 0475 effect->filter().set("out", out); 0476 roles << TimelineModel::FadeOutRole; 0477 } 0478 bool res = local_redo(); 0479 if (res) { 0480 Fun update = [this, roles]() { 0481 Q_EMIT dataChanged(QModelIndex(), QModelIndex(), roles); 0482 return true; 0483 }; 0484 update(); 0485 if (logUndo) { 0486 PUSH_LAMBDA(update, local_redo); 0487 PUSH_LAMBDA(update, local_undo); 0488 pCore->pushUndo(local_undo, local_redo, i18n("Paste effect")); 0489 } 0490 } 0491 return res; 0492 } 0493 0494 bool EffectStackModel::appendEffectWithUndo(const QString &effectId, Fun &undo, Fun &redo) 0495 { 0496 return doAppendEffect(effectId, false, {}, undo, redo); 0497 } 0498 0499 bool EffectStackModel::appendEffect(const QString &effectId, bool makeCurrent, stringMap params) 0500 { 0501 Fun undo = []() { return true; }; 0502 Fun redo = []() { return true; }; 0503 bool result = doAppendEffect(effectId, makeCurrent, params, undo, redo); 0504 if (result) { 0505 PUSH_UNDO(undo, redo, i18n("Add effect %1", EffectsRepository::get()->getName(effectId))); 0506 } 0507 return result; 0508 } 0509 0510 bool EffectStackModel::doAppendEffect(const QString &effectId, bool makeCurrent, stringMap params, Fun &undo, Fun &redo) 0511 { 0512 QWriteLocker locker(&m_lock); 0513 if (m_ownerId.type == KdenliveObjectType::TimelineClip && EffectsRepository::get()->isUnique(effectId) && hasEffect(effectId)) { 0514 pCore->displayMessage(i18n("Effect %1 cannot be added twice.", EffectsRepository::get()->getName(effectId)), ErrorMessage); 0515 return false; 0516 } 0517 std::unordered_set<int> previousFadeIn = m_fadeIns; 0518 std::unordered_set<int> previousFadeOut = m_fadeOuts; 0519 if (EffectsRepository::get()->isGroup(effectId)) { 0520 QDomElement doc = EffectsRepository::get()->getXml(effectId); 0521 return copyXmlEffect(doc); 0522 } 0523 auto effect = EffectItemModel::construct(effectId, shared_from_this()); 0524 PlaylistState::ClipState state = pCore->getItemState(m_ownerId); 0525 if (state == PlaylistState::VideoOnly) { 0526 if (effect->isAudio()) { 0527 // Cannot add effect to this clip 0528 pCore->displayMessage(i18n("Cannot add effect to clip"), ErrorMessage); 0529 return false; 0530 } 0531 } else if (state == PlaylistState::AudioOnly) { 0532 if (!effect->isAudio()) { 0533 // Cannot add effect to this clip 0534 pCore->displayMessage(i18n("Cannot add effect to clip"), ErrorMessage); 0535 return false; 0536 } 0537 } 0538 QMapIterator<QString, QString> i(params); 0539 while (i.hasNext()) { 0540 i.next(); 0541 effect->filter().set(i.key().toUtf8().constData(), i.value().toUtf8().constData()); 0542 } 0543 Fun local_undo = removeItem_lambda(effect->getId()); 0544 // TODO the parent should probably not always be the root 0545 Fun local_redo = addItem_lambda(effect, rootItem->getId()); 0546 effect->prepareKeyframes(); 0547 connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged); 0548 connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection); 0549 connect(effect.get(), &AssetParameterModel::showEffectZone, this, &EffectStackModel::updateEffectZones); 0550 int currentActive = getActiveEffect(); 0551 if (makeCurrent) { 0552 setActiveEffect(rowCount()); 0553 } 0554 bool res = local_redo(); 0555 if (res) { 0556 int inFades = 0; 0557 int outFades = 0; 0558 if (effectId.startsWith(QLatin1String("fadein")) || effectId.startsWith(QLatin1String("fade_from_"))) { 0559 int duration = effect->filter().get_length() - 1; 0560 int in = pCore->getItemIn(m_ownerId); 0561 effect->filter().set("in", in); 0562 effect->filter().set("out", in + duration); 0563 inFades++; 0564 } else if (effectId.startsWith(QLatin1String("fadeout")) || effectId.startsWith(QLatin1String("fade_to_"))) { 0565 /*int duration = effect->filter().get_length() - 1; 0566 int out = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1; 0567 effect->filter().set("in", out - duration); 0568 effect->filter().set("out", out);*/ 0569 outFades++; 0570 } else if (m_ownerId.type == KdenliveObjectType::TimelineTrack) { 0571 effect->filter().set("out", pCore->getItemDuration(m_ownerId)); 0572 } 0573 Fun update = [this, inFades, outFades]() { 0574 // TODO: only update if effect is fade or keyframe 0575 QVector<int> roles = {TimelineModel::EffectNamesRole}; 0576 if (inFades > 0) { 0577 roles << TimelineModel::FadeInRole; 0578 } else if (outFades > 0) { 0579 roles << TimelineModel::FadeOutRole; 0580 } 0581 pCore->updateItemKeyframes(m_ownerId); 0582 Q_EMIT dataChanged(QModelIndex(), QModelIndex(), roles); 0583 return true; 0584 }; 0585 Fun update_undo = [this, inFades, outFades, previousFadeIn, previousFadeOut]() { 0586 // TODO: only update if effect is fade or keyframe 0587 QVector<int> roles = {TimelineModel::EffectNamesRole}; 0588 if (inFades > 0) { 0589 m_fadeIns = previousFadeIn; 0590 roles << TimelineModel::FadeInRole; 0591 } else if (outFades > 0) { 0592 m_fadeOuts = previousFadeOut; 0593 roles << TimelineModel::FadeOutRole; 0594 } 0595 pCore->updateItemKeyframes(m_ownerId); 0596 Q_EMIT dataChanged(QModelIndex(), QModelIndex(), roles); 0597 return true; 0598 }; 0599 update(); 0600 PUSH_LAMBDA(update, local_redo); 0601 PUSH_LAMBDA(update_undo, local_undo); 0602 PUSH_LAMBDA(local_redo, redo); 0603 PUSH_LAMBDA(local_undo, undo); 0604 } else if (makeCurrent) { 0605 setActiveEffect(currentActive); 0606 } 0607 return res; 0608 } 0609 0610 bool EffectStackModel::adjustStackLength(bool adjustFromEnd, int oldIn, int oldDuration, int newIn, int duration, int offset, Fun &undo, Fun &redo, 0611 bool logUndo) 0612 { 0613 QWriteLocker locker(&m_lock); 0614 const int fadeInDuration = getFadePosition(true); 0615 const int fadeOutDuration = getFadePosition(false); 0616 int out = newIn + duration; 0617 for (const auto &leaf : rootItem->getLeaves()) { 0618 std::shared_ptr<AbstractEffectItem> item = std::static_pointer_cast<AbstractEffectItem>(leaf); 0619 if (item->effectItemType() == EffectItemType::Group) { 0620 // probably an empty group, ignore 0621 continue; 0622 } 0623 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(leaf); 0624 if (fadeInDuration > 0 && m_fadeIns.count(leaf->getId()) > 0) { 0625 // Adjust fade in 0626 int oldEffectIn = qMax(0, effect->filter().get_in()); 0627 int oldEffectOut = effect->filter().get_out(); 0628 qDebug() << "--previous effect: " << oldEffectIn << "-" << oldEffectOut; 0629 int effectDuration = qMin(effect->filter().get_length() - 1, duration); 0630 if (!adjustFromEnd && (oldIn != newIn || duration != oldDuration)) { 0631 // Clip start was resized, adjust effect in / out 0632 Fun operation = [effect, newIn, effectDuration, logUndo]() { 0633 effect->setParameter(QStringLiteral("in"), newIn, false); 0634 effect->setParameter(QStringLiteral("out"), newIn + effectDuration, logUndo); 0635 qDebug() << "--new effect: " << newIn << "-" << newIn + effectDuration; 0636 return true; 0637 }; 0638 bool res = operation(); 0639 if (!res) { 0640 return false; 0641 } 0642 Fun reverse = [effect, oldEffectIn, oldEffectOut, logUndo]() { 0643 effect->setParameter(QStringLiteral("in"), oldEffectIn, false); 0644 effect->setParameter(QStringLiteral("out"), oldEffectOut, logUndo); 0645 return true; 0646 }; 0647 PUSH_LAMBDA(operation, redo); 0648 PUSH_LAMBDA(reverse, undo); 0649 } else if (effectDuration < oldEffectOut - oldEffectIn || (logUndo && effect->filter().get_int("_refout") > 0)) { 0650 // Clip length changed, shorter than effect length so resize 0651 int referenceEffectOut = effect->filter().get_int("_refout"); 0652 if (referenceEffectOut <= 0) { 0653 referenceEffectOut = oldEffectOut; 0654 effect->filter().set("_refout", referenceEffectOut); 0655 } 0656 Fun operation = [effect, oldEffectIn, effectDuration, logUndo]() { 0657 effect->setParameter(QStringLiteral("out"), oldEffectIn + effectDuration, logUndo); 0658 return true; 0659 }; 0660 bool res = operation(); 0661 if (!res) { 0662 return false; 0663 } 0664 if (logUndo) { 0665 Fun reverse = [effect, referenceEffectOut]() { 0666 effect->setParameter(QStringLiteral("out"), referenceEffectOut, true); 0667 effect->filter().set("_refout", nullptr); 0668 return true; 0669 }; 0670 PUSH_LAMBDA(operation, redo); 0671 PUSH_LAMBDA(reverse, undo); 0672 } 0673 } 0674 } else if (fadeOutDuration > 0 && m_fadeOuts.count(leaf->getId()) > 0) { 0675 // Adjust fade out 0676 int effectDuration = qMin(fadeOutDuration, duration); 0677 int newFadeIn = out - effectDuration; 0678 int oldFadeIn = effect->filter().get_int("in"); 0679 int oldOut = effect->filter().get_int("out"); 0680 int referenceEffectIn = effect->filter().get_int("_refin"); 0681 if (referenceEffectIn <= 0) { 0682 referenceEffectIn = oldFadeIn; 0683 effect->filter().set("_refin", referenceEffectIn); 0684 } 0685 Fun operation = [effect, newFadeIn, out, logUndo]() { 0686 effect->setParameter(QStringLiteral("in"), newFadeIn, false); 0687 effect->setParameter(QStringLiteral("out"), out, logUndo); 0688 return true; 0689 }; 0690 bool res = operation(); 0691 if (!res) { 0692 return false; 0693 } 0694 if (logUndo) { 0695 Fun reverse = [effect, referenceEffectIn, oldOut]() { 0696 effect->setParameter(QStringLiteral("in"), referenceEffectIn, false); 0697 effect->setParameter(QStringLiteral("out"), oldOut, true); 0698 effect->filter().set("_refin", nullptr); 0699 return true; 0700 }; 0701 PUSH_LAMBDA(operation, redo); 0702 PUSH_LAMBDA(reverse, undo); 0703 } 0704 } else { 0705 // Not a fade effect, check for keyframes 0706 bool hasZone = effect->filter().get_int("kdenlive:force_in_out") == 1; 0707 std::shared_ptr<KeyframeModelList> keyframes = effect->getKeyframeModel(); 0708 if (keyframes != nullptr) { 0709 // Effect has keyframes, update these 0710 keyframes->resizeKeyframes(oldIn, oldIn + oldDuration, newIn, out - 1, offset, adjustFromEnd, undo, redo); 0711 QModelIndex index = getIndexFromItem(effect); 0712 Fun refresh = [effect, index]() { 0713 Q_EMIT effect->dataChanged(index, QModelIndex(), QVector<int>()); 0714 return true; 0715 }; 0716 refresh(); 0717 PUSH_LAMBDA(refresh, redo); 0718 PUSH_LAMBDA(refresh, undo); 0719 } else { 0720 qDebug() << "// NULL Keyframes---------"; 0721 } 0722 if (m_ownerId.type == KdenliveObjectType::TimelineTrack && !hasZone) { 0723 int oldEffectOut = effect->filter().get_out(); 0724 Fun operation = [effect, out, logUndo]() { 0725 effect->setParameter(QStringLiteral("out"), out, logUndo); 0726 return true; 0727 }; 0728 bool res = operation(); 0729 if (!res) { 0730 return false; 0731 } 0732 if (logUndo) { 0733 Fun reverse = [effect, oldEffectOut]() { 0734 effect->setParameter(QStringLiteral("out"), oldEffectOut, true); 0735 return true; 0736 }; 0737 PUSH_LAMBDA(operation, redo); 0738 PUSH_LAMBDA(reverse, undo); 0739 } 0740 } else if (m_ownerId.type == KdenliveObjectType::TimelineClip && effect->data(QModelIndex(), AssetParameterModel::RequiresInOut).toBool() == true) { 0741 int oldEffectIn = qMax(0, effect->filter().get_in()); 0742 int oldEffectOut = effect->filter().get_out(); 0743 int newIn = pCore->getItemIn(m_ownerId); 0744 int newOut = newIn + pCore->getItemDuration(m_ownerId) - 1; 0745 Fun operation = [effect, newIn, newOut]() { 0746 effect->filter().set_in_and_out(newIn, newOut); 0747 qDebug() << "--new effect: " << newIn << "-" << newOut; 0748 return true; 0749 }; 0750 bool res = operation(); 0751 if (!res) { 0752 return false; 0753 } 0754 Fun reverse = [effect, oldEffectIn, oldEffectOut]() { 0755 effect->filter().set_in_and_out(oldEffectIn, oldEffectOut); 0756 return true; 0757 }; 0758 PUSH_LAMBDA(operation, redo); 0759 PUSH_LAMBDA(reverse, undo); 0760 } 0761 } 0762 } 0763 return true; 0764 } 0765 0766 bool EffectStackModel::adjustFadeLength(int duration, bool fromStart, bool audioFade, bool videoFade, bool logUndo) 0767 { 0768 QWriteLocker locker(&m_lock); 0769 if (fromStart) { 0770 // Fade in 0771 if (m_fadeIns.empty()) { 0772 if (audioFade) { 0773 appendEffect(QStringLiteral("fadein")); 0774 } 0775 if (videoFade) { 0776 appendEffect(QStringLiteral("fade_from_black")); 0777 } 0778 } 0779 QList<QModelIndex> indexes; 0780 auto ptr = m_masterService.lock(); 0781 int in = 0; 0782 if (ptr) { 0783 in = ptr->get_int("in"); 0784 } 0785 int oldDuration = -1; 0786 for (int i = 0; i < rootItem->childCount(); ++i) { 0787 if (m_fadeIns.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0) { 0788 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 0789 if (oldDuration == -1) { 0790 oldDuration = effect->filter().get_length(); 0791 } 0792 effect->filter().set("in", in); 0793 duration = qMin(pCore->getItemDuration(m_ownerId), duration); 0794 effect->filter().set("out", in + duration); 0795 indexes << getIndexFromItem(effect); 0796 if (effect->filter().get("alpha") == QLatin1String("1")) { 0797 // Adjust level value to match filter end 0798 effect->filter().set("level", "0=0;-1=1"); 0799 } else if (effect->filter().get("level") == QLatin1String("1")) { 0800 effect->filter().set("alpha", "0=0;-1=1"); 0801 } 0802 } 0803 } 0804 if (!indexes.isEmpty()) { 0805 Q_EMIT dataChanged(indexes.first(), indexes.last(), QVector<int>()); 0806 pCore->updateItemModel(m_ownerId, QStringLiteral("fadein")); 0807 if (videoFade) { 0808 int min = pCore->getItemPosition(m_ownerId); 0809 QPair<int, int> range = {min, min + qMax(duration, oldDuration)}; 0810 pCore->refreshProjectRange(range); 0811 if (logUndo) { 0812 pCore->invalidateRange(range); 0813 } 0814 } 0815 } 0816 } else { 0817 // Fade out 0818 if (m_fadeOuts.empty()) { 0819 if (audioFade) { 0820 appendEffect(QStringLiteral("fadeout")); 0821 } 0822 if (videoFade) { 0823 appendEffect(QStringLiteral("fade_to_black")); 0824 } 0825 } 0826 int in = 0; 0827 auto ptr = m_masterService.lock(); 0828 if (ptr) { 0829 in = ptr->get_int("in"); 0830 } 0831 int itemDuration = pCore->getItemDuration(m_ownerId); 0832 int out = in + itemDuration - 1; 0833 int oldDuration = -1; 0834 QList<QModelIndex> indexes; 0835 for (int i = 0; i < rootItem->childCount(); ++i) { 0836 if (m_fadeOuts.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0) { 0837 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 0838 if (oldDuration == -1) { 0839 oldDuration = effect->filter().get_length(); 0840 } 0841 effect->filter().set("out", out); 0842 duration = qMin(itemDuration, duration); 0843 effect->filter().set("in", out - duration); 0844 indexes << getIndexFromItem(effect); 0845 if (effect->filter().get("alpha") == QLatin1String("1")) { 0846 // Adjust level value to match filter end 0847 effect->filter().set("level", "0=1;-1=0"); 0848 } else if (effect->filter().get("level") == QLatin1String("1")) { 0849 effect->filter().set("alpha", "0=1;-1=0"); 0850 } 0851 } 0852 } 0853 if (!indexes.isEmpty()) { 0854 Q_EMIT dataChanged(indexes.first(), indexes.last(), QVector<int>()); 0855 pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout")); 0856 if (videoFade) { 0857 int min = pCore->getItemPosition(m_ownerId); 0858 QPair<int, int> range = {min + itemDuration - qMax(duration, oldDuration), min + itemDuration}; 0859 pCore->refreshProjectRange(range); 0860 if (logUndo) { 0861 pCore->invalidateRange(range); 0862 } 0863 } 0864 } 0865 } 0866 return true; 0867 } 0868 0869 int EffectStackModel::getFadePosition(bool fromStart) 0870 { 0871 QWriteLocker locker(&m_lock); 0872 if (fromStart) { 0873 if (m_fadeIns.empty()) { 0874 return 0; 0875 } 0876 for (int i = 0; i < rootItem->childCount(); ++i) { 0877 if (*(m_fadeIns.begin()) == std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) { 0878 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 0879 return effect->filter().get_length() - 1; 0880 } 0881 } 0882 } else { 0883 if (m_fadeOuts.empty()) { 0884 return 0; 0885 } 0886 for (int i = 0; i < rootItem->childCount(); ++i) { 0887 if (*(m_fadeOuts.begin()) == std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) { 0888 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 0889 return effect->filter().get_length() - 1; 0890 } 0891 } 0892 } 0893 return 0; 0894 } 0895 0896 bool EffectStackModel::removeFade(bool fromStart) 0897 { 0898 QWriteLocker locker(&m_lock); 0899 std::vector<int> toRemove; 0900 for (int i = 0; i < rootItem->childCount(); ++i) { 0901 if ((fromStart && m_fadeIns.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0) || 0902 (!fromStart && m_fadeOuts.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0)) { 0903 toRemove.push_back(i); 0904 } 0905 } 0906 // Let's put index in reverse order so we don't mess when deleting 0907 std::reverse(toRemove.begin(), toRemove.end()); 0908 for (int i : toRemove) { 0909 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 0910 removeEffect(effect); 0911 } 0912 return true; 0913 } 0914 0915 void EffectStackModel::moveEffectByRow(int destRow, int srcRow) 0916 { 0917 moveEffect(destRow, getEffectStackRow(srcRow)); 0918 } 0919 0920 void EffectStackModel::moveEffect(int destRow, const std::shared_ptr<AbstractEffectItem> &item) 0921 { 0922 QWriteLocker locker(&m_lock); 0923 Q_ASSERT(m_allItems.count(item->getId()) > 0); 0924 int oldRow = item->row(); 0925 Fun undo = moveItem_lambda(item->getId(), oldRow); 0926 Fun redo = moveItem_lambda(item->getId(), destRow); 0927 bool res = redo(); 0928 if (res) { 0929 Fun update = [this]() { 0930 Q_EMIT this->dataChanged(QModelIndex(), QModelIndex(), {TimelineModel::EffectNamesRole}); 0931 return true; 0932 }; 0933 update(); 0934 UPDATE_UNDO_REDO(update, update, undo, redo); 0935 auto effectId = std::static_pointer_cast<EffectItemModel>(item)->getAssetId(); 0936 PUSH_UNDO(undo, redo, i18n("Move effect %1", EffectsRepository::get()->getName(effectId))); 0937 } 0938 } 0939 0940 void EffectStackModel::registerItem(const std::shared_ptr<TreeItem> &item) 0941 { 0942 QWriteLocker locker(&m_lock); 0943 // qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting effect"; 0944 if (!item->isRoot()) { 0945 auto effectItem = std::static_pointer_cast<EffectItemModel>(item); 0946 if (effectItem->data(QModelIndex(), AssetParameterModel::RequiresInOut).toBool() == true) { 0947 int in = pCore->getItemIn(m_ownerId); 0948 int out = in + pCore->getItemDuration(m_ownerId) - 1; 0949 effectItem->filter().set_in_and_out(in, out); 0950 } 0951 if (!m_loadingExisting) { 0952 // qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting effect in " << m_childServices.size(); 0953 effectItem->plant(m_masterService); 0954 // Check if we have an internal effect that needs to stay on top 0955 if (m_ownerId.type == KdenliveObjectType::Master || m_ownerId.type == KdenliveObjectType::TimelineTrack) { 0956 // check for subtitle effect 0957 auto ms = m_masterService.lock(); 0958 int ct = ms->filter_count(); 0959 QVector<int> ixToMove; 0960 for (int i = 0; i < ct; i++) { 0961 if (ms->filter(i)->get_int("internal_added") > 0) { 0962 ixToMove << i; 0963 } 0964 } 0965 std::sort(ixToMove.rbegin(), ixToMove.rend()); 0966 for (auto &ix : ixToMove) { 0967 if (ix < ct - 1) { 0968 ms->move_filter(ix, ct - 1); 0969 } 0970 } 0971 } 0972 for (const auto &service : m_childServices) { 0973 // qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting CLONE effect in " << (void *)service.lock().get(); 0974 effectItem->plantClone(service); 0975 } 0976 } 0977 effectItem->setEffectStackEnabled(m_effectStackEnabled); 0978 const QString &effectId = effectItem->getAssetId(); 0979 if (effectId.startsWith(QLatin1String("fadein")) || effectId.startsWith(QLatin1String("fade_from_"))) { 0980 m_fadeIns.insert(effectItem->getId()); 0981 } else if (effectId.startsWith(QLatin1String("fadeout")) || effectId.startsWith(QLatin1String("fade_to_"))) { 0982 m_fadeOuts.insert(effectItem->getId()); 0983 } 0984 if (!effectItem->isAudio() && !m_loadingExisting) { 0985 pCore->refreshProjectItem(m_ownerId); 0986 pCore->invalidateItem(m_ownerId); 0987 } 0988 } 0989 AbstractTreeModel::registerItem(item); 0990 } 0991 0992 void EffectStackModel::deregisterItem(int id, TreeItem *item) 0993 { 0994 QWriteLocker locker(&m_lock); 0995 if (!item->isRoot()) { 0996 auto effectItem = static_cast<AbstractEffectItem *>(item); 0997 effectItem->unplant(m_masterService); 0998 for (const auto &service : m_childServices) { 0999 effectItem->unplantClone(service); 1000 } 1001 if (!effectItem->isAudio()) { 1002 pCore->refreshProjectItem(m_ownerId); 1003 pCore->invalidateItem(m_ownerId); 1004 } 1005 } 1006 AbstractTreeModel::deregisterItem(id, item); 1007 } 1008 1009 void EffectStackModel::setEffectStackEnabled(bool enabled) 1010 { 1011 QWriteLocker locker(&m_lock); 1012 m_effectStackEnabled = enabled; 1013 1014 QList<QModelIndex> indexes; 1015 // Recursively updates children states 1016 for (int i = 0; i < rootItem->childCount(); ++i) { 1017 std::shared_ptr<AbstractEffectItem> item = std::static_pointer_cast<AbstractEffectItem>(rootItem->child(i)); 1018 item->setEffectStackEnabled(enabled); 1019 indexes << getIndexFromItem(item); 1020 } 1021 if (indexes.isEmpty()) { 1022 return; 1023 } 1024 pCore->refreshProjectItem(m_ownerId); 1025 pCore->invalidateItem(m_ownerId); 1026 Q_EMIT dataChanged(indexes.first(), indexes.last(), {TimelineModel::EffectsEnabledRole}); 1027 Q_EMIT enabledStateChanged(); 1028 } 1029 1030 std::shared_ptr<AbstractEffectItem> EffectStackModel::getEffectStackRow(int row, const std::shared_ptr<TreeItem> &parentItem) 1031 { 1032 return std::static_pointer_cast<AbstractEffectItem>(parentItem ? parentItem->child(row) : rootItem->child(row)); 1033 } 1034 1035 bool EffectStackModel::importEffects(const std::shared_ptr<EffectStackModel> &sourceStack, PlaylistState::ClipState state) 1036 { 1037 QWriteLocker locker(&m_lock); 1038 // TODO: manage fades, keyframes if clips don't have same size / in point 1039 bool found = false; 1040 bool effectEnabled = rootItem->childCount() > 0; 1041 int imported = 0; 1042 for (int i = 0; i < sourceStack->rowCount(); i++) { 1043 auto item = sourceStack->getEffectStackRow(i); 1044 // NO undo. this should only be used on project opening 1045 if (copyEffect(item, state, false)) { 1046 found = true; 1047 if (item->isEnabled()) { 1048 effectEnabled = true; 1049 } 1050 imported++; 1051 } 1052 } 1053 if (!effectEnabled && imported == 0) { 1054 effectEnabled = true; 1055 } 1056 m_effectStackEnabled = effectEnabled; 1057 if (!m_effectStackEnabled) { 1058 // Mark all effects as disabled by stack 1059 for (int i = 0; i < rootItem->childCount(); ++i) { 1060 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 1061 sourceEffect->setEffectStackEnabled(false); 1062 sourceEffect->setEnabled(true); 1063 } 1064 } 1065 if (found) { 1066 Q_EMIT modelChanged(); 1067 } 1068 return found; 1069 } 1070 1071 void EffectStackModel::importEffects(const std::weak_ptr<Mlt::Service> &service, PlaylistState::ClipState state, bool alreadyExist, 1072 const QString &originalDecimalPoint, const QUuid &uuid) 1073 { 1074 QWriteLocker locker(&m_lock); 1075 m_loadingExisting = alreadyExist; 1076 bool effectEnabled = true; 1077 if (auto ptr = service.lock()) { 1078 int max = ptr->filter_count(); 1079 int imported = 0; 1080 for (int i = 0; i < max; i++) { 1081 std::unique_ptr<Mlt::Filter> filter(ptr->filter(i)); 1082 if (filter->get_int("internal_added") > 0 && m_ownerId.type != KdenliveObjectType::TimelineTrack) { 1083 // Required to load master audio effects 1084 if (m_ownerId.type == KdenliveObjectType::Master && filter->get("mlt_service") == QLatin1String("avfilter.subtitles")) { 1085 // A subtitle filter, update project 1086 QMap<QString, QString> subProperties; 1087 // subProperties.insert(QStringLiteral("av.filename"), filter->get("av.filename")); 1088 subProperties.insert(QStringLiteral("disable"), filter->get("disable")); 1089 subProperties.insert(QStringLiteral("kdenlive:locked"), filter->get("kdenlive:locked")); 1090 const QString style = filter->get("av.force_style"); 1091 if (!style.isEmpty()) { 1092 subProperties.insert(QStringLiteral("av.force_style"), style); 1093 } 1094 pCore->window()->slotInitSubtitle(subProperties, uuid); 1095 } else if (auto ms = m_masterService.lock()) { 1096 ms->attach(*filter.get()); 1097 } 1098 continue; 1099 } 1100 if (!filter->property_exists("kdenlive_id")) { 1101 // don't consider internal MLT stuff 1102 continue; 1103 } 1104 const QString effectId = qstrdup(filter->get("kdenlive_id")); 1105 if (m_ownerId.type == KdenliveObjectType::TimelineClip && EffectsRepository::get()->isUnique(effectId) && hasEffect(effectId)) { 1106 pCore->displayMessage(i18n("Effect %1 cannot be added twice.", EffectsRepository::get()->getName(effectId)), ErrorMessage); 1107 continue; 1108 } 1109 if (filter->get_int("disable") == 0) { 1110 effectEnabled = true; 1111 } 1112 // The MLT filter already exists, use it directly to create the effect 1113 std::shared_ptr<EffectItemModel> effect; 1114 if (alreadyExist) { 1115 // effect is already plugged in the service 1116 effect = EffectItemModel::construct(std::move(filter), shared_from_this(), originalDecimalPoint); 1117 } else { 1118 // duplicate effect 1119 std::unique_ptr<Mlt::Filter> asset = EffectsRepository::get()->getEffect(effectId); 1120 asset->inherit(*(filter)); 1121 effect = EffectItemModel::construct(std::move(asset), shared_from_this(), originalDecimalPoint); 1122 } 1123 if (state == PlaylistState::VideoOnly) { 1124 if (effect->isAudio()) { 1125 // Don't import effect 1126 continue; 1127 } 1128 } else if (state == PlaylistState::AudioOnly) { 1129 if (!effect->isAudio()) { 1130 // Don't import effect 1131 continue; 1132 } 1133 } 1134 imported++; 1135 connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged); 1136 connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection); 1137 connect(effect.get(), &AssetParameterModel::showEffectZone, this, &EffectStackModel::updateEffectZones); 1138 Fun redo = addItem_lambda(effect, rootItem->getId()); 1139 int clipIn = ptr->get_int("in"); 1140 int clipOut = ptr->get_int("out"); 1141 if (clipOut <= clipIn) { 1142 clipOut = ptr->get_int("length") - 1; 1143 } 1144 effect->prepareKeyframes(clipIn, clipOut); 1145 if (redo()) { 1146 if (effectId.startsWith(QLatin1String("fadein")) || effectId.startsWith(QLatin1String("fade_from_"))) { 1147 m_fadeIns.insert(effect->getId()); 1148 if (effect->filter().get_int("in") != clipIn) { 1149 // Broken fade, fix 1150 int filterLength = effect->filter().get_length() - 1; 1151 effect->filter().set("in", clipIn); 1152 effect->filter().set("out", clipIn + filterLength); 1153 } 1154 } else if (effectId.startsWith(QLatin1String("fadeout")) || effectId.startsWith(QLatin1String("fade_to_"))) { 1155 m_fadeOuts.insert(effect->getId()); 1156 if (effect->filter().get_int("out") != clipOut) { 1157 // Broken fade, fix 1158 int filterLength = effect->filter().get_length() - 1; 1159 effect->filter().set("in", clipOut - filterLength); 1160 effect->filter().set("out", clipOut); 1161 } 1162 } 1163 } 1164 } 1165 if (imported == 0) { 1166 effectEnabled = true; 1167 } 1168 } 1169 m_effectStackEnabled = effectEnabled; 1170 if (!m_effectStackEnabled) { 1171 // Mark all effects as disabled by stack 1172 for (int i = 0; i < rootItem->childCount(); ++i) { 1173 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 1174 sourceEffect->setEffectStackEnabled(false); 1175 sourceEffect->setEnabled(true); 1176 } 1177 } 1178 m_loadingExisting = false; 1179 Q_EMIT modelChanged(); 1180 } 1181 1182 void EffectStackModel::setActiveEffect(int ix) 1183 { 1184 QWriteLocker locker(&m_lock); 1185 int current = -1; 1186 if (auto ptr = m_masterService.lock()) { 1187 current = ptr->get_int("kdenlive:activeeffect"); 1188 ptr->set("kdenlive:activeeffect", ix); 1189 } 1190 // Desactivate previous effect 1191 if (current > -1 && current != ix && current < rootItem->childCount()) { 1192 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(current)); 1193 if (effect) { 1194 effect->setActive(false); 1195 Q_EMIT currentChanged(getIndexFromItem(effect), false); 1196 } 1197 } 1198 // Activate new effect 1199 if (ix > -1 && ix < rootItem->childCount()) { 1200 std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix)); 1201 if (effect) { 1202 effect->setActive(true); 1203 Q_EMIT currentChanged(getIndexFromItem(effect), true); 1204 } 1205 } 1206 pCore->updateItemKeyframes(m_ownerId); 1207 } 1208 1209 int EffectStackModel::getActiveEffect() const 1210 { 1211 QWriteLocker locker(&m_lock); 1212 if (auto ptr = m_masterService.lock()) { 1213 return ptr->get_int("kdenlive:activeeffect"); 1214 } 1215 return 0; 1216 } 1217 1218 void EffectStackModel::slotCreateGroup(const std::shared_ptr<EffectItemModel> &childEffect) 1219 { 1220 QWriteLocker locker(&m_lock); 1221 auto groupItem = EffectGroupModel::construct(QStringLiteral("group"), shared_from_this()); 1222 rootItem->appendChild(groupItem); 1223 groupItem->appendChild(childEffect); 1224 } 1225 1226 ObjectId EffectStackModel::getOwnerId() const 1227 { 1228 return m_ownerId; 1229 } 1230 1231 bool EffectStackModel::checkConsistency() 1232 { 1233 if (!AbstractTreeModel::checkConsistency()) { 1234 return false; 1235 } 1236 1237 std::vector<std::shared_ptr<EffectItemModel>> allFilters; 1238 // We do a DFS on the tree to retrieve all the filters 1239 std::stack<std::shared_ptr<AbstractEffectItem>> stck; 1240 stck.push(std::static_pointer_cast<AbstractEffectItem>(rootItem)); 1241 1242 while (!stck.empty()) { 1243 auto current = stck.top(); 1244 stck.pop(); 1245 1246 if (current->effectItemType() == EffectItemType::Effect) { 1247 if (current->childCount() > 0) { 1248 qDebug() << "ERROR: Found an effect with children"; 1249 return false; 1250 } 1251 allFilters.push_back(std::static_pointer_cast<EffectItemModel>(current)); 1252 continue; 1253 } 1254 for (int i = current->childCount() - 1; i >= 0; --i) { 1255 stck.push(std::static_pointer_cast<AbstractEffectItem>(current->child(i))); 1256 } 1257 } 1258 1259 for (const auto &service : m_childServices) { 1260 auto ptr = service.lock(); 1261 if (!ptr) { 1262 qDebug() << "ERROR: unavailable service"; 1263 return false; 1264 } 1265 // MLT inserts some default normalizer filters that are not managed by Kdenlive, which explains why the filter count is not equal 1266 int kdenliveFilterCount = 0; 1267 for (int i = 0; i < ptr->filter_count(); i++) { 1268 std::shared_ptr<Mlt::Filter> filt(ptr->filter(i)); 1269 if (filt->property_exists("kdenlive_id")) { 1270 kdenliveFilterCount++; 1271 } 1272 // qDebug() << "FILTER: "<<i<<" : "<<ptr->filter(i)->get("mlt_service"); 1273 } 1274 if (kdenliveFilterCount != int(allFilters.size())) { 1275 qDebug() << "ERROR: Wrong filter count: " << kdenliveFilterCount << " = " << allFilters.size(); 1276 return false; 1277 } 1278 1279 int ct = 0; 1280 for (uint i = 0; i < allFilters.size(); ++i) { 1281 while (ptr->filter(ct)->get("kdenlive_id") == nullptr && ct < ptr->filter_count()) { 1282 ct++; 1283 } 1284 auto mltFilter = ptr->filter(ct); 1285 auto currentFilter = allFilters[i]->filter(); 1286 if (QString(currentFilter.get("mlt_service")) != QLatin1String(mltFilter->get("mlt_service"))) { 1287 qDebug() << "ERROR: filter " << i << "differ: " << ct << ", " << currentFilter.get("mlt_service") << " = " << mltFilter->get("mlt_service"); 1288 return false; 1289 } 1290 QVector<QPair<QString, QVariant>> params = allFilters[i]->getAllParameters(); 1291 for (const auto &val : qAsConst(params)) { 1292 // Check parameters values 1293 if (val.second.toString() != QString(mltFilter->get(val.first.toUtf8().constData()))) { 1294 qDebug() << "ERROR: filter " << i << "PARAMETER MISMATCH: " << val.first << " = " << val.second 1295 << " != " << mltFilter->get(val.first.toUtf8().constData()); 1296 return false; 1297 } 1298 } 1299 ct++; 1300 } 1301 } 1302 return true; 1303 } 1304 1305 void EffectStackModel::adjust(const QString &effectId, const QString &effectName, double value) 1306 { 1307 QWriteLocker locker(&m_lock); 1308 for (int i = 0; i < rootItem->childCount(); ++i) { 1309 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 1310 if (effectId == sourceEffect->getAssetId()) { 1311 sourceEffect->setParameter(effectName, QString::number(value)); 1312 return; 1313 } 1314 } 1315 } 1316 1317 std::shared_ptr<AssetParameterModel> EffectStackModel::getAssetModelById(const QString &effectId) 1318 { 1319 QWriteLocker locker(&m_lock); 1320 for (int i = 0; i < rootItem->childCount(); ++i) { 1321 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 1322 if (effectId == sourceEffect->getAssetId()) { 1323 return std::static_pointer_cast<AssetParameterModel>(sourceEffect); 1324 } 1325 } 1326 return nullptr; 1327 } 1328 1329 bool EffectStackModel::hasFilter(const QString &effectId) const 1330 { 1331 READ_LOCK(); 1332 return rootItem->accumulate_const(false, [effectId](bool b, std::shared_ptr<const TreeItem> it) { 1333 if (b) return true; 1334 auto item = std::static_pointer_cast<const AbstractEffectItem>(it); 1335 if (item->effectItemType() == EffectItemType::Group) { 1336 return false; 1337 } 1338 auto sourceEffect = std::static_pointer_cast<const EffectItemModel>(it); 1339 return effectId == sourceEffect->getAssetId(); 1340 }); 1341 } 1342 1343 double EffectStackModel::getFilterParam(const QString &effectId, const QString ¶mName) 1344 { 1345 READ_LOCK(); 1346 for (int i = 0; i < rootItem->childCount(); ++i) { 1347 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 1348 if (effectId == sourceEffect->getAssetId()) { 1349 return sourceEffect->filter().get_double(paramName.toUtf8().constData()); 1350 } 1351 } 1352 return 0.0; 1353 } 1354 1355 KeyframeModel *EffectStackModel::getEffectKeyframeModel() 1356 { 1357 if (rootItem->childCount() == 0) return nullptr; 1358 int ix = getActiveEffect(); 1359 if (ix < 0 || ix >= rootItem->childCount()) { 1360 return nullptr; 1361 } 1362 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix)); 1363 std::shared_ptr<KeyframeModelList> listModel = sourceEffect->getKeyframeModel(); 1364 if (listModel) { 1365 return listModel->getKeyModel(); 1366 } 1367 return nullptr; 1368 } 1369 1370 void EffectStackModel::replugEffect(const std::shared_ptr<AssetParameterModel> &asset) 1371 { 1372 QWriteLocker locker(&m_lock); 1373 auto effectItem = std::static_pointer_cast<EffectItemModel>(asset); 1374 int oldRow = effectItem->row(); 1375 int count = rowCount(); 1376 for (int ix = oldRow; ix < count; ix++) { 1377 auto item = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix)); 1378 item->unplant(m_masterService); 1379 for (const auto &service : m_childServices) { 1380 item->unplantClone(service); 1381 } 1382 } 1383 std::unique_ptr<Mlt::Properties> effect = EffectsRepository::get()->getEffect(effectItem->getAssetId()); 1384 effect->inherit(effectItem->filter()); 1385 effectItem->resetAsset(std::move(effect)); 1386 for (int ix = oldRow; ix < count; ix++) { 1387 auto item = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix)); 1388 item->plant(m_masterService); 1389 for (const auto &service : m_childServices) { 1390 item->plantClone(service); 1391 } 1392 } 1393 } 1394 1395 void EffectStackModel::cleanFadeEffects(bool outEffects, Fun &undo, Fun &redo) 1396 { 1397 QWriteLocker locker(&m_lock); 1398 const auto &toDelete = outEffects ? m_fadeOuts : m_fadeIns; 1399 for (int id : toDelete) { 1400 auto effect = std::static_pointer_cast<EffectItemModel>(getItemById(id)); 1401 Fun operation = removeItem_lambda(id); 1402 if (operation()) { 1403 Fun reverse = addItem_lambda(effect, rootItem->getId()); 1404 UPDATE_UNDO_REDO(operation, reverse, undo, redo); 1405 } 1406 } 1407 if (!toDelete.empty()) { 1408 Fun updateRedo = [this, toDelete, outEffects]() { 1409 for (int id : toDelete) { 1410 if (outEffects) { 1411 m_fadeOuts.erase(id); 1412 } else { 1413 m_fadeIns.erase(id); 1414 } 1415 } 1416 QVector<int> roles = {TimelineModel::EffectNamesRole}; 1417 roles << (outEffects ? TimelineModel::FadeOutRole : TimelineModel::FadeInRole); 1418 Q_EMIT dataChanged(QModelIndex(), QModelIndex(), roles); 1419 pCore->updateItemKeyframes(m_ownerId); 1420 return true; 1421 }; 1422 updateRedo(); 1423 PUSH_LAMBDA(updateRedo, redo); 1424 } 1425 } 1426 1427 const QString EffectStackModel::effectNames() const 1428 { 1429 QStringList effects; 1430 for (int i = 0; i < rootItem->childCount(); ++i) { 1431 effects.append(EffectsRepository::get()->getName(std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->getAssetId())); 1432 } 1433 return effects.join(QLatin1Char('/')); 1434 } 1435 1436 QStringList EffectStackModel::externalFiles() const 1437 { 1438 QStringList urls; 1439 for (int i = 0; i < rootItem->childCount(); ++i) { 1440 auto filter = std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->filter(); 1441 QString url; 1442 if (filter.property_exists("av.file")) { 1443 url = filter.get("av.file"); 1444 } else if (filter.property_exists("luma.resource")) { 1445 url = filter.get("luma.resource"); 1446 } else if (filter.property_exists("resource")) { 1447 url = filter.get("resource"); 1448 } 1449 if (!url.isEmpty()) { 1450 urls << url; 1451 } 1452 } 1453 urls.removeDuplicates(); 1454 return urls; 1455 } 1456 1457 bool EffectStackModel::isStackEnabled() const 1458 { 1459 return m_effectStackEnabled; 1460 } 1461 1462 bool EffectStackModel::addEffectKeyFrame(int frame, double normalisedVal) 1463 { 1464 if (rootItem->childCount() == 0) return false; 1465 int ix = getActiveEffect(); 1466 if (ix < 0) { 1467 return false; 1468 } 1469 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix)); 1470 std::shared_ptr<KeyframeModelList> listModel = sourceEffect->getKeyframeModel(); 1471 if (m_ownerId.type == KdenliveObjectType::TimelineTrack) { 1472 sourceEffect->filter().set("out", pCore->getItemDuration(m_ownerId)); 1473 } 1474 return listModel->addKeyframe(frame, normalisedVal); 1475 } 1476 1477 bool EffectStackModel::removeKeyFrame(int frame) 1478 { 1479 if (rootItem->childCount() == 0) return false; 1480 int ix = getActiveEffect(); 1481 if (ix < 0) { 1482 return false; 1483 } 1484 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix)); 1485 std::shared_ptr<KeyframeModelList> listModel = sourceEffect->getKeyframeModel(); 1486 return listModel->removeKeyframe(GenTime(frame, pCore->getCurrentFps())); 1487 } 1488 1489 bool EffectStackModel::updateKeyFrame(int oldFrame, int newFrame, QVariant normalisedVal) 1490 { 1491 if (rootItem->childCount() == 0) return false; 1492 int ix = getActiveEffect(); 1493 if (ix < 0) { 1494 return false; 1495 } 1496 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix)); 1497 std::shared_ptr<KeyframeModelList> listModel = sourceEffect->getKeyframeModel(); 1498 if (m_ownerId.type == KdenliveObjectType::TimelineTrack) { 1499 sourceEffect->filter().set("out", pCore->getItemDuration(m_ownerId)); 1500 } 1501 return listModel->updateKeyframe(GenTime(oldFrame, pCore->getCurrentFps()), GenTime(newFrame, pCore->getCurrentFps()), std::move(normalisedVal)); 1502 } 1503 1504 bool EffectStackModel::hasKeyFrame(int frame) 1505 { 1506 if (rootItem->childCount() == 0) return false; 1507 int ix = getActiveEffect(); 1508 if (ix < 0) { 1509 return false; 1510 } 1511 std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix)); 1512 std::shared_ptr<KeyframeModelList> listModel = sourceEffect->getKeyframeModel(); 1513 return listModel->hasKeyframe(frame); 1514 } 1515 1516 bool EffectStackModel::hasEffect(const QString &assetId) const 1517 { 1518 for (int i = 0; i < rootItem->childCount(); ++i) { 1519 if (std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->getAssetId() == assetId) { 1520 return true; 1521 } 1522 } 1523 return false; 1524 } 1525 1526 QVariantList EffectStackModel::getEffectZones() const 1527 { 1528 QVariantList effectZones; 1529 for (int i = 0; i < rootItem->childCount(); ++i) { 1530 auto item = std::static_pointer_cast<EffectItemModel>(rootItem->child(i)); 1531 if (item->hasForcedInOut()) { 1532 QPair<int, int> z = item->getInOut(); 1533 effectZones << QPoint(z.first, z.second); 1534 } 1535 } 1536 return effectZones; 1537 } 1538 1539 void EffectStackModel::updateEffectZones() 1540 { 1541 Q_EMIT dataChanged(QModelIndex(), QModelIndex(), {TimelineModel::EffectZonesRole}); 1542 if (m_ownerId.type == KdenliveObjectType::Master) { 1543 Q_EMIT updateMasterZones(); 1544 } 1545 } 1546 1547 void EffectStackModel::passEffects(Mlt::Producer *producer, const QString &exception) 1548 { 1549 auto ms = m_masterService.lock(); 1550 int ct = ms->filter_count(); 1551 for (int i = 0; i < ct; i++) { 1552 if (ms->filter(i)->get_int("internal_added") > 0 || !ms->filter(i)->property_exists("kdenlive_id")) { 1553 continue; 1554 } 1555 if (!exception.isEmpty() && QString(ms->filter(i)->get("mlt_service")) == exception) { 1556 continue; 1557 } 1558 auto *filter = new Mlt::Filter(*ms->filter(i)); 1559 producer->attach(*filter); 1560 } 1561 }