From ddd31bbef74b9c1ec47fadf966094e6ec6072f08 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Mardelle Date: Fri, 5 Dec 2025 12:36:07 +0100 Subject: [PATCH] When copy/paste between 2 kdenlive windows, correctly load bin clip effects --- .../effectstack/model/effectstackmodel.cpp | 22 +++++ .../effectstack/model/effectstackmodel.hpp | 2 + src/mltcontroller/clipcontroller.cpp | 96 ++++++++++--------- src/mltcontroller/clipcontroller.h | 1 + src/xml/xml.cpp | 2 +- 5 files changed, 78 insertions(+), 45 deletions(-) diff --git a/src/effects/effectstack/model/effectstackmodel.cpp b/src/effects/effectstack/model/effectstackmodel.cpp index 907dfbe049..e7bcfb0731 100644 --- a/src/effects/effectstack/model/effectstackmodel.cpp +++ b/src/effects/effectstack/model/effectstackmodel.cpp @@ -386,6 +386,28 @@ QDomElement EffectStackModel::rowToXml(int row, QDomDocument &document) return container; } +bool EffectStackModel::fromMltXml(const QDomElement &effectsXml) +{ + QDomNodeList nodeList = effectsXml.elementsByTagName(QStringLiteral("filter")); + for (int i = 0; i < nodeList.count(); ++i) { + QDomElement node = nodeList.item(i).toElement(); + if (!Xml::hasXmlProperty(node, QStringLiteral("kdenlive_id")) || Xml::hasXmlProperty(node, QStringLiteral("internal_added"))) { + // Internal effect, ignore + continue; + } + if (Xml::hasXmlProperty(node, QStringLiteral("kdenlive:builtin")) && Xml::getXmlProperty(node, QStringLiteral("disable")) == QLatin1String("1")) { + // Disabled built-in effect, ignore + continue; + } + const QString effectId = Xml::getXmlProperty(node, QStringLiteral("kdenlive_id")); + Fun undo = []() { return true; }; + Fun redo = []() { return true; }; + stringMap params = Xml::getXmlPropertyByWildcard(node, QString()); + doAppendEffect(effectId, false, params, undo, redo); + } + return true; +} + bool EffectStackModel::fromXml(const QDomElement &effectsXml, Fun &undo, Fun &redo) { QDomNodeList nodeList = effectsXml.elementsByTagName(QStringLiteral("effect")); diff --git a/src/effects/effectstack/model/effectstackmodel.hpp b/src/effects/effectstack/model/effectstackmodel.hpp index efe9b23a61..b3e300d014 100644 --- a/src/effects/effectstack/model/effectstackmodel.hpp +++ b/src/effects/effectstack/model/effectstackmodel.hpp @@ -137,6 +137,8 @@ public: QDomElement rowToXml(int row, QDomDocument &document); /** @brief Load an effect stack from an XML representation */ bool fromXml(const QDomElement &effectsXml, Fun &undo, Fun &redo); + /** @brief Load an effect stack from an MLT XML representation */ + bool fromMltXml(const QDomElement &effectsXml); /** @brief Delete active effect from stack */ void removeCurrentEffect(); diff --git a/src/mltcontroller/clipcontroller.cpp b/src/mltcontroller/clipcontroller.cpp index bf9a52e2ef..e8fa0fef3d 100644 --- a/src/mltcontroller/clipcontroller.cpp +++ b/src/mltcontroller/clipcontroller.cpp @@ -69,6 +69,9 @@ ClipController::ClipController(const QString &clipId, const std::shared_ptr 0) { + m_effectsToLoad = description; + } } } @@ -123,56 +126,61 @@ void ClipController::addMasterProducer(const std::shared_ptr &pro if (!m_masterProducer->is_valid()) { m_masterProducer = std::shared_ptr(pCore->mediaUnavailable->cut()); qCDebug(KDENLIVE_LOG) << "// WARNING, USING INVALID PRODUCER"; - } else { - setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId); - if (!m_properties->property_exists("kdenlive:control_uuid")) { - m_properties->set("kdenlive:control_uuid", m_controlUuid.toString().toUtf8().constData()); - } - getInfoForProducer(); - checkAudioVideo(); - if (!m_hasMultipleVideoStreams && m_service.startsWith(QLatin1String("avformat")) && (m_clipType == ClipType::AV || m_clipType == ClipType::Video)) { - // Check if clip has multiple video streams - QList videoStreams; - QList audioStreams; - int aStreams = m_properties->get_int("meta.media.nb_streams"); - for (int ix = 0; ix < aStreams; ++ix) { - char property[200]; - snprintf(property, sizeof(property), "meta.media.%d.stream.type", ix); - QString type = m_properties->get(property); - if (type == QLatin1String("video")) { - QString key = QStringLiteral("meta.media.%1.codec.name").arg(ix); - QString codec_name = m_properties->get(key.toLatin1().constData()); - if (codec_name == QLatin1String("png")) { + connectEffectStack(); + return; + } + setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId); + if (!m_properties->property_exists("kdenlive:control_uuid")) { + m_properties->set("kdenlive:control_uuid", m_controlUuid.toString().toUtf8().constData()); + } + getInfoForProducer(); + checkAudioVideo(); + if (!m_effectsToLoad.isNull()) { + m_effectStack->fromMltXml(m_effectsToLoad); + m_effectsToLoad.clear(); + } + if (!m_hasMultipleVideoStreams && m_service.startsWith(QLatin1String("avformat")) && (m_clipType == ClipType::AV || m_clipType == ClipType::Video)) { + // Check if clip has multiple video streams + QList videoStreams; + QList audioStreams; + int aStreams = m_properties->get_int("meta.media.nb_streams"); + for (int ix = 0; ix < aStreams; ++ix) { + char property[200]; + snprintf(property, sizeof(property), "meta.media.%d.stream.type", ix); + QString type = m_properties->get(property); + if (type == QLatin1String("video")) { + QString key = QStringLiteral("meta.media.%1.codec.name").arg(ix); + QString codec_name = m_properties->get(key.toLatin1().constData()); + if (codec_name == QLatin1String("png")) { + // This is a cover image, skip + qDebug() << "=== FOUND PNG COVER ART STREAM: " << ix; + setProducerProperty(QStringLiteral("kdenlive:coverartstream"), ix); + continue; + } + if (codec_name == QLatin1String("mjpeg")) { + key = QStringLiteral("meta.media.%1.stream.frame_rate").arg(ix); + QString fps = m_properties->get(key.toLatin1().constData()); + if (fps.isEmpty()) { + key = QStringLiteral("meta.media.%1.codec.frame_rate").arg(ix); + fps = m_properties->get(key.toLatin1().constData()); + } + if (fps == QLatin1String("90000")) { // This is a cover image, skip - qDebug() << "=== FOUND PNG COVER ART STREAM: " << ix; + qDebug() << "=== FOUND MJPEG COVER ART STREAM: " << ix; setProducerProperty(QStringLiteral("kdenlive:coverartstream"), ix); continue; } - if (codec_name == QLatin1String("mjpeg")) { - key = QStringLiteral("meta.media.%1.stream.frame_rate").arg(ix); - QString fps = m_properties->get(key.toLatin1().constData()); - if (fps.isEmpty()) { - key = QStringLiteral("meta.media.%1.codec.frame_rate").arg(ix); - fps = m_properties->get(key.toLatin1().constData()); - } - if (fps == QLatin1String("90000")) { - // This is a cover image, skip - qDebug() << "=== FOUND MJPEG COVER ART STREAM: " << ix; - setProducerProperty(QStringLiteral("kdenlive:coverartstream"), ix); - continue; - } - } - videoStreams << ix; - } else if (type == QLatin1String("audio")) { - audioStreams << ix; } + videoStreams << ix; + } else if (type == QLatin1String("audio")) { + audioStreams << ix; } - if (videoStreams.count() > 1) { - setProducerProperty(QStringLiteral("kdenlive:multistreams"), 1); - m_hasMultipleVideoStreams = true; - QMetaObject::invokeMethod(pCore->bin(), "processMultiStream", Qt::QueuedConnection, Q_ARG(QString, m_controllerBinId), - Q_ARG(QList, videoStreams), Q_ARG(QList, audioStreams)); - } + } + if (videoStreams.count() > 1) { + setProducerProperty(QStringLiteral("kdenlive:multistreams"), 1); + m_hasMultipleVideoStreams = true; + QMetaObject::invokeMethod(pCore->bin(), "processMultiStream", Qt::QueuedConnection, Q_ARG(QString, m_controllerBinId), + Q_ARG(QList, videoStreams), Q_ARG(QList, audioStreams)); } } connectEffectStack(); diff --git a/src/mltcontroller/clipcontroller.h b/src/mltcontroller/clipcontroller.h index df3936735a..742b864a90 100644 --- a/src/mltcontroller/clipcontroller.h +++ b/src/mltcontroller/clipcontroller.h @@ -251,6 +251,7 @@ private: /** @brief Temporarily store clip properties until producer is available */ QMap m_tempProps; QString m_controllerBinId; + QDomElement m_effectsToLoad; /** @brief Build the audio info object */ void buildAudioInfo(int audioIndex); }; diff --git a/src/xml/xml.cpp b/src/xml/xml.cpp index da7662a3cd..1e1a6d7b26 100644 --- a/src/xml/xml.cpp +++ b/src/xml/xml.cpp @@ -218,7 +218,7 @@ QMap Xml::getXmlPropertyByWildcard(const QDomElement &element, QDomNodeList params = element.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); - if (e.attribute(QStringLiteral("name")).startsWith(propertyName)) { + if (propertyName.isEmpty() || e.attribute(QStringLiteral("name")).startsWith(propertyName)) { props.insert(e.attribute(QStringLiteral("name")), e.text()); } }