File indexing completed on 2024-12-01 04:28:36
0001 /* 0002 SPDX-FileCopyrightText: 2011 Jean-Baptiste Mardelle <jb@kdenlive.org> 0003 0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "filtertask.h" 0008 #include "assets/model/assetparametermodel.hpp" 0009 #include "bin/bin.h" 0010 #include "bin/projectclip.h" 0011 #include "bin/projectitemmodel.h" 0012 #include "core.h" 0013 #include "effects/effectstack/model/effectstackmodel.hpp" 0014 #include "kdenlive_debug.h" 0015 #include "kdenlivesettings.h" 0016 #include "macros.hpp" 0017 #include "mainwindow.h" 0018 #include "profiles/profilemodel.hpp" 0019 #include "xml/xml.hpp" 0020 0021 #include <QProcess> 0022 #include <QThread> 0023 0024 #include <KLocalizedString> 0025 0026 FilterTask::FilterTask(const ObjectId &owner, const QString &binId, const std::weak_ptr<AssetParameterModel> &model, const QString &assetId, int in, int out, 0027 const QString &filterName, const std::unordered_map<QString, QVariant> &filterParams, 0028 const std::unordered_map<QString, QString> &filterData, const QStringList &consumerArgs, QObject *object) 0029 : AbstractTask(owner, AbstractTask::FILTERCLIPJOB, object) 0030 , length(0) 0031 , m_binId(binId) 0032 , m_inPoint(in) 0033 , m_outPoint(out) 0034 , m_assetId(assetId) 0035 , m_model(model) 0036 , m_filterName(filterName) 0037 , m_filterParams(filterParams) 0038 , m_filterData(filterData) 0039 , m_consumerArgs(consumerArgs) 0040 { 0041 m_description = i18n("Processing filter %1", filterName); 0042 } 0043 0044 void FilterTask::start(const ObjectId &owner, const QString &binId, const std::weak_ptr<AssetParameterModel> &model, const QString &assetId, int in, int out, 0045 const QString &filterName, const std::unordered_map<QString, QVariant> &filterParams, 0046 const std::unordered_map<QString, QString> &filterData, const QStringList &consumerArgs, QObject *object, bool force) 0047 { 0048 FilterTask *task = new FilterTask(owner, binId, model, assetId, in, out, filterName, filterParams, filterData, consumerArgs, object); 0049 // Otherwise, start a filter thread. 0050 task->m_isForce = force; 0051 pCore->taskManager.startTask(owner.itemId, task); 0052 } 0053 0054 void FilterTask::run() 0055 { 0056 AbstractTaskDone whenFinished(m_owner.itemId, this); 0057 if (m_isCanceled || pCore->taskManager.isBlocked()) { 0058 return; 0059 } 0060 QMutexLocker lock(&m_runMutex); 0061 m_running = true; 0062 0063 QString url; 0064 auto binClip = pCore->projectItemModel()->getClipByBinID(m_binId); 0065 std::unique_ptr<Mlt::Producer> producer = nullptr; 0066 Mlt::Profile profile(pCore->getCurrentProfilePath().toUtf8().constData()); 0067 if (binClip) { 0068 // Filter applied on a timeline or bin clip 0069 url = binClip->url(); 0070 if (url.isEmpty()) { 0071 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("No producer for this clip.")), 0072 Q_ARG(int, int(KMessageWidget::Warning))); 0073 return; 0074 } 0075 if (KdenliveSettings::gpu_accel()) { 0076 producer = binClip->getClone(); 0077 if (m_outPoint == -1) { 0078 m_outPoint = producer->get_length() - 1; 0079 } 0080 if (m_inPoint == -1) { 0081 m_inPoint = 0; 0082 } 0083 if (m_inPoint != 0 || m_outPoint != producer->get_length() - 1) { 0084 producer->set_in_and_out(m_inPoint, m_outPoint); 0085 } 0086 Mlt::Filter converter(profile, "avcolor_space"); 0087 producer->attach(converter); 0088 } else { 0089 producer = std::make_unique<Mlt::Producer>(profile, url.toUtf8().constData()); 0090 if (!producer || !producer->is_valid()) { 0091 if (!binClip->isReloading) { 0092 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, 0093 Q_ARG(QString, i18n("Cannot open file %1", binClip->url())), Q_ARG(int, int(KMessageWidget::Warning))); 0094 } else { 0095 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, 0096 Q_ARG(QString, i18n("Cannot process file %1", binClip->url())), Q_ARG(int, int(KMessageWidget::Warning))); 0097 } 0098 } 0099 if (m_outPoint == -1) { 0100 m_outPoint = producer->get_length() - 1; 0101 } 0102 if (m_inPoint == -1) { 0103 m_inPoint = 0; 0104 } 0105 if (m_inPoint != 0 || m_outPoint != producer->get_length() - 1) { 0106 producer->set_in_and_out(m_inPoint, m_outPoint); 0107 } 0108 // Ensure all user defined properties are passed 0109 const char *list = ClipController::getPassPropertiesList(); 0110 std::shared_ptr<Mlt::Producer> sourceProducer = binClip->originalProducer(); 0111 Mlt::Properties original(sourceProducer->get_properties()); 0112 Mlt::Properties cloneProps(producer->get_properties()); 0113 cloneProps.pass_list(original, list); 0114 for (int i = 0; i < sourceProducer->filter_count(); i++) { 0115 std::shared_ptr<Mlt::Filter> filt(sourceProducer->filter(i)); 0116 if (filt->property_exists("kdenlive_id")) { 0117 auto *filter = new Mlt::Filter(*filt.get()); 0118 producer->attach(*filter); 0119 } 0120 } 0121 if (m_owner.type == KdenliveObjectType::TimelineClip) { 0122 // Add the timeline clip effects 0123 std::shared_ptr<EffectStackModel> stack = pCore->getItemEffectStack(pCore->currentTimelineId(), int(m_owner.type), m_owner.itemId); 0124 stack->passEffects(producer.get(), m_filterName); 0125 } 0126 } 0127 if ((producer == nullptr) || !producer->is_valid()) { 0128 // Clip was removed or something went wrong 0129 if (!binClip->isReloading) { 0130 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("Cannot open file %1", binClip->url())), 0131 Q_ARG(int, int(KMessageWidget::Warning))); 0132 } else { 0133 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, 0134 Q_ARG(QString, i18n("Cannot process file %1", binClip->url())), Q_ARG(int, int(KMessageWidget::Warning))); 0135 } 0136 return; 0137 } 0138 } else { 0139 // Filter applied on a track of master producer, leave config to source job 0140 // We are on master or track, configure producer accordingly 0141 if (m_owner.type == KdenliveObjectType::Master) { 0142 producer = pCore->getMasterProducerInstance(); 0143 } else if (m_owner.type == KdenliveObjectType::TimelineTrack) { 0144 producer = pCore->getTrackProducerInstance(m_owner.itemId); 0145 } 0146 } 0147 0148 if (producer == nullptr || !producer->is_valid()) { 0149 // Clip was removed or something went wrong, Notify user? 0150 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("Cannot open source.")), 0151 Q_ARG(int, int(KMessageWidget::Warning))); 0152 return; 0153 } 0154 length = producer->get_playtime(); 0155 if (length == 0) { 0156 length = producer->get_length(); 0157 } 0158 0159 // Build consumer 0160 QTemporaryFile sourceFile(QDir::temp().absoluteFilePath(QStringLiteral("kdenlive-XXXXXX.mlt"))); 0161 if (!sourceFile.open()) { 0162 // Something went wrong 0163 return; 0164 } 0165 sourceFile.close(); 0166 QTemporaryFile destFile(QDir::temp().absoluteFilePath(QStringLiteral("kdenlive-XXXXXX.mlt"))); 0167 if (!destFile.open()) { 0168 // Something went wrong 0169 return; 0170 } 0171 destFile.close(); 0172 std::unique_ptr<Mlt::Consumer> consumer(new Mlt::Consumer(profile, "xml", sourceFile.fileName().toUtf8().constData())); 0173 if (!consumer->is_valid()) { 0174 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("Cannot create consumer.")), 0175 Q_ARG(int, int(KMessageWidget::Warning))); 0176 return; 0177 } 0178 0179 consumer->connect(*producer.get()); 0180 producer->set_speed(0); 0181 0182 if (binClip) { 0183 // Build filter 0184 Mlt::Filter filter(profile, m_filterName.toUtf8().data()); 0185 if (!filter.is_valid()) { 0186 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("Cannot create filter %1", m_filterName)), 0187 Q_ARG(int, int(KMessageWidget::Warning))); 0188 return; 0189 } 0190 0191 // Process filter params 0192 qDebug() << " = = = = = CONFIGURING FILTER PARAMS = = = = = "; 0193 for (const auto &it : m_filterParams) { 0194 qDebug() << ". . ." << it.first << " = " << it.second; 0195 if (it.first == QLatin1String("in") || it.first == QLatin1String("out")) { 0196 continue; 0197 } 0198 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0199 if (it.second.type() == QVariant::Double) { 0200 #else 0201 if (it.second.typeId() == QMetaType::Double) { 0202 #endif 0203 filter.set(it.first.toUtf8().constData(), it.second.toDouble()); 0204 } else { 0205 filter.set(it.first.toUtf8().constData(), it.second.toString().toUtf8().constData()); 0206 } 0207 } 0208 if (m_filterData.find(QLatin1String("relativeInOut")) != m_filterData.end()) { 0209 // leave it operate on full clip 0210 } else { 0211 filter.set_in_and_out(m_inPoint, m_outPoint); 0212 } 0213 producer->attach(filter); 0214 filter.set("kdenlive:id", "kdenlive-analysis"); 0215 } 0216 0217 qDebug() << "=== FILTER READY TO PROCESS; LENGTH: " << length; 0218 consumer->run(); 0219 consumer.reset(); 0220 producer.reset(); 0221 // wholeProducer.reset(); 0222 0223 QDomDocument dom(sourceFile.fileName()); 0224 Xml::docContentFromFile(dom, sourceFile.fileName(), false); 0225 0226 // add consumer element 0227 QDomElement consumerNode = dom.createElement("consumer"); 0228 QDomNodeList profiles = dom.elementsByTagName("profile"); 0229 if (profiles.isEmpty()) { 0230 dom.documentElement().insertAfter(consumerNode, dom.documentElement()); 0231 } else { 0232 dom.documentElement().insertAfter(consumerNode, profiles.at(profiles.length() - 1)); 0233 } 0234 consumerNode.setAttribute("mlt_service", "xml"); 0235 for (const QString ¶m : qAsConst(m_consumerArgs)) { 0236 if (param.contains(QLatin1Char('='))) { 0237 consumerNode.setAttribute(param.section(QLatin1Char('='), 0, 0), param.section(QLatin1Char('='), 1)); 0238 } 0239 } 0240 consumerNode.setAttribute("resource", destFile.fileName()); 0241 consumerNode.setAttribute("store", "kdenlive"); 0242 0243 QFile f1(sourceFile.fileName()); 0244 f1.open(QIODevice::WriteOnly); 0245 QTextStream stream(&f1); 0246 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0247 stream.setCodec("UTF-8"); 0248 #endif 0249 stream << dom.toString(); 0250 f1.close(); 0251 dom.clear(); 0252 0253 // Step 2: process the xml file and save in another .mlt file 0254 QStringList args({QStringLiteral("progress=1"), sourceFile.fileName()}); 0255 m_jobProcess.reset(new QProcess); 0256 QObject::connect(this, &AbstractTask::jobCanceled, m_jobProcess.get(), &QProcess::kill, Qt::DirectConnection); 0257 QObject::connect(m_jobProcess.get(), &QProcess::readyReadStandardError, this, &FilterTask::processLogInfo); 0258 m_jobProcess->start(KdenliveSettings::meltpath(), args); 0259 m_jobProcess->waitForFinished(-1); 0260 bool result = m_jobProcess->exitStatus() == QProcess::NormalExit; 0261 m_progress = 100; 0262 if (auto ptr = m_model.lock()) { 0263 QMetaObject::invokeMethod(ptr.get(), "setProgress", Q_ARG(int, 100)); 0264 } 0265 if (m_isCanceled || !result) { 0266 if (!m_isCanceled) { 0267 QMetaObject::invokeMethod(pCore.get(), "displayBinLogMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("Failed to filter source.")), 0268 Q_ARG(int, int(KMessageWidget::Warning)), Q_ARG(QString, m_logDetails)); 0269 } 0270 return; 0271 } 0272 0273 paramVector params; 0274 QString key("results"); 0275 if (m_filterData.find(QStringLiteral("key")) != m_filterData.end()) { 0276 key = m_filterData.at(QStringLiteral("key")); 0277 } 0278 0279 QString resultData; 0280 if (Xml::docContentFromFile(dom, destFile.fileName(), false)) { 0281 qDebug() << "AAAA\nGOT DOC\n" << dom.toString(); 0282 QDomNodeList filters = dom.elementsByTagName(QLatin1String("filter")); 0283 for (int i = 0; i < filters.count(); ++i) { 0284 QDomElement currentParameter = filters.item(i).toElement(); 0285 if (Xml::getXmlProperty(currentParameter, QLatin1String("mlt_service")) == m_filterName) { 0286 resultData = Xml::getXmlProperty(currentParameter, key); 0287 } else if (Xml::getXmlProperty(currentParameter, QLatin1String("kdenlive:id")) == QLatin1String("kdenlive-analysis")) { 0288 resultData = Xml::getXmlProperty(currentParameter, key); 0289 } 0290 if (!resultData.isEmpty()) { 0291 break; 0292 } 0293 } 0294 } 0295 0296 if (m_inPoint > 0 && (m_filterData.find(QLatin1String("relativeInOut")) == m_filterData.end())) { 0297 // Motion tracker keyframes always start at master clip 0, so no need to set in/out points 0298 params.append({QStringLiteral("in"), m_inPoint}); 0299 params.append({QStringLiteral("out"), m_outPoint}); 0300 } 0301 params.append({key, QVariant(resultData)}); 0302 if (m_filterData.find(QStringLiteral("storedata")) != m_filterData.end()) { 0303 // Store a copy of the data in clip analysis 0304 QString dataName = (m_filterData.find(QStringLiteral("displaydataname")) != m_filterData.end()) ? m_filterData.at(QStringLiteral("displaydataname")) 0305 : QStringLiteral("data"); 0306 auto binClip = pCore->projectItemModel()->getClipByBinID(m_binId); 0307 if (binClip) { 0308 QMetaObject::invokeMethod(binClip.get(), "updatedAnalysisData", Q_ARG(QString, dataName), Q_ARG(QString, resultData), Q_ARG(int, m_inPoint)); 0309 } 0310 // binClip->updatedAnalysisData(dataName, resultData, m_inPoint); 0311 } 0312 if (auto ptr = m_model.lock()) { 0313 qDebug() << "===== SETTING FILTER PARAM: " << params; 0314 QMetaObject::invokeMethod(ptr.get(), "setParametersFromTask", Q_ARG(paramVector, std::move(params))); 0315 } 0316 } 0317 0318 void FilterTask::processLogInfo() 0319 { 0320 const QString buffer = QString::fromUtf8(m_jobProcess->readAllStandardError()); 0321 m_logDetails.append(buffer); 0322 // Parse MLT output 0323 if (buffer.contains(QLatin1String("percentage:"))) { 0324 int progress = buffer.section(QStringLiteral("percentage:"), 1).simplified().section(QLatin1Char(' '), 0, 0).toInt(); 0325 if (progress == m_progress) { 0326 return; 0327 } 0328 if (auto ptr = m_model.lock()) { 0329 m_progress = progress; 0330 QMetaObject::invokeMethod(ptr.get(), "setProgress", Q_ARG(int, m_progress)); 0331 } 0332 } 0333 }