File indexing completed on 2024-12-01 04:28:37
0001 /* 0002 SPDX-FileCopyrightText: 2021 Jean-Baptiste Mardelle <jb@kdenlive.org> 0003 0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "stabilizetask.h" 0008 #include "assets/model/assetparametermodel.hpp" 0009 #include "bin/bin.h" 0010 #include "bin/projectclip.h" 0011 #include "bin/projectfolder.h" 0012 #include "bin/projectitemmodel.h" 0013 #include "core.h" 0014 #include "kdenlive_debug.h" 0015 #include "kdenlivesettings.h" 0016 #include "macros.hpp" 0017 #include "mainwindow.h" 0018 #include "profiles/profilemodel.hpp" 0019 #include "project/clipstabilize.h" 0020 #include "xml/xml.hpp" 0021 0022 #include <QProcess> 0023 #include <QThread> 0024 0025 #include <KLocalizedString> 0026 0027 StabilizeTask::StabilizeTask(const ObjectId &owner, const QString &binId, const QString &destination, int in, int out, 0028 const std::unordered_map<QString, QVariant> &filterParams, QObject *object) 0029 : AbstractTask(owner, AbstractTask::STABILIZEJOB, object) 0030 , m_binId(binId) 0031 , m_inPoint(in) 0032 , m_outPoint(out) 0033 , m_filterParams(filterParams) 0034 , m_destination(destination) 0035 { 0036 m_description = i18n("Stabilizing"); 0037 } 0038 0039 void StabilizeTask::start(QObject *, bool force) 0040 { 0041 std::vector<QString> binIds = pCore->bin()->selectedClipsIds(true); 0042 QScopedPointer<ClipStabilize> d(new ClipStabilize(binIds, QStringLiteral("vidstab"), QApplication::activeWindow())); 0043 if (d->exec() == QDialog::Accepted) { 0044 std::unordered_map<QString, QVariant> filterParams = d->filterParams(); 0045 std::unordered_map<QString, QString> destinations; // keys are binIds, values are path to target files 0046 for (const auto &binId : binIds) { 0047 qDebug() << "==== ANALYSING BINID: " << binId; 0048 auto binClip = pCore->projectItemModel()->getClipByBinID(binId.section(QLatin1Char('/'), 0, 0)); 0049 QString mltfile = binClip->url() + QStringLiteral(".mlt"); 0050 destinations[binId] = mltfile; 0051 } 0052 // Now we have to create the jobs objects. This is trickier than usual, since the parameters are different for each job (each clip has its own 0053 // destination). We have to construct a lambda that does that. 0054 for (auto &id : binIds) { 0055 StabilizeTask *task = nullptr; 0056 ObjectId owner; 0057 if (id.contains(QLatin1Char('/'))) { 0058 QStringList binData = id.split(QLatin1Char('/')); 0059 if (binData.size() < 3) { 0060 // Invalid subclip data 0061 qDebug() << "=== INVALID SUBCLIP DATA: " << id; 0062 continue; 0063 } 0064 owner = ObjectId(KdenliveObjectType::BinClip, binData.first().toInt(), QUuid()); 0065 auto binClip = pCore->projectItemModel()->getClipByBinID(binData.first()); 0066 if (binClip) { 0067 task = new StabilizeTask(owner, binData.first(), destinations.at(id), binData.at(1).toInt(), binData.at(2).toInt(), filterParams, 0068 binClip.get()); 0069 } 0070 } else { 0071 // Process full clip 0072 owner = ObjectId(KdenliveObjectType::BinClip, id.toInt(), QUuid()); 0073 auto binClip = pCore->projectItemModel()->getClipByBinID(id); 0074 if (binClip) { 0075 task = new StabilizeTask(owner, id, destinations.at(id), -1, -1, filterParams, binClip.get()); 0076 } 0077 } 0078 if (task) { 0079 // Otherwise, start a filter thread. 0080 task->m_isForce = force; 0081 pCore->taskManager.startTask(owner.itemId, task); 0082 } 0083 } 0084 } 0085 } 0086 0087 void StabilizeTask::run() 0088 { 0089 AbstractTaskDone whenFinished(m_owner.itemId, this); 0090 if (m_isCanceled || pCore->taskManager.isBlocked()) { 0091 return; 0092 } 0093 QMutexLocker lock(&m_runMutex); 0094 m_running = true; 0095 qDebug() << " + + + + + + + + STARTING STAB TASK"; 0096 0097 QString url; 0098 auto binClip = pCore->projectItemModel()->getClipByBinID(m_binId); 0099 QString folderId = QLatin1String("-1"); 0100 QStringList producerArgs = {QStringLiteral("progress=1"), QStringLiteral("-profile"), pCore->getCurrentProfilePath()}; 0101 if (binClip) { 0102 // Filter applied on a timeline or bin clip 0103 folderId = binClip->parent()->clipId(); 0104 url = binClip->url(); 0105 if (url.isEmpty()) { 0106 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("No producer for this clip.")), 0107 Q_ARG(int, int(KMessageWidget::Warning))); 0108 return; 0109 } 0110 producerArgs << url; 0111 producerArgs << binClip->enforcedParams(); 0112 0113 if (m_inPoint > -1) { 0114 producerArgs << QString("in=%1").arg(m_inPoint); 0115 } 0116 if (m_outPoint > -1) { 0117 producerArgs << QString("out=%1").arg(m_outPoint); 0118 } 0119 } else { 0120 // Filter applied on a track of master producer, leave config to source job 0121 // We are on master or track, configure producer accordingly 0122 // TODO 0123 QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("No producer for this clip.")), 0124 Q_ARG(int, int(KMessageWidget::Warning))); 0125 return; 0126 /*if (m_owner.type == KdenliveObjectType::Master) { 0127 producer = pCore->getMasterProducerInstance(); 0128 } else if (m_owner.type == KdenliveObjectType::TimelineTrack) { 0129 producer = pCore->getTrackProducerInstance(m_owner.second); 0130 } 0131 if ((producer == nullptr) || !producer->is_valid()) { 0132 // Clip was removed or something went wrong, Notify user? 0133 m_errorMessage.append(i18n("Invalid clip")); 0134 return; 0135 }*/ 0136 } 0137 0138 producerArgs << QStringLiteral("-attach") << QStringLiteral("vidstab"); 0139 0140 // Process filter params 0141 qDebug() << " = = = = = CONFIGURING FILTER PARAMS = = = = = "; 0142 for (const auto &it : m_filterParams) { 0143 qDebug() << ". . ." << it.first << " = " << it.second; 0144 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0145 if (it.second.type() == QVariant::Double) { 0146 #else 0147 if (it.second.typeId() == QMetaType::Double) { 0148 #endif 0149 producerArgs << QString("%1=%2").arg(it.first, QString::number(it.second.toDouble())); 0150 } else { 0151 producerArgs << QString("%1=%2").arg(it.first, it.second.toString()); 0152 } 0153 } 0154 QString targetFile = m_destination + QStringLiteral(".trf"); 0155 int count = 1; 0156 while (QFile::exists(targetFile)) { 0157 targetFile = m_destination + QString("-%1.trf").arg(count); 0158 count++; 0159 } 0160 producerArgs << QString("filename=%1").arg(targetFile); 0161 0162 // Start the MLT Process 0163 QProcess filterProcess; 0164 producerArgs << QStringLiteral("-consumer") << QString("xml:%1").arg(m_destination) << QStringLiteral("all=1") << QStringLiteral("terminate_on_pause=1"); 0165 m_jobProcess.reset(new QProcess); 0166 QMetaObject::invokeMethod(m_object, "updateJobProgress"); 0167 QObject::connect(this, &AbstractTask::jobCanceled, m_jobProcess.get(), &QProcess::kill, Qt::DirectConnection); 0168 QObject::connect(m_jobProcess.get(), &QProcess::readyReadStandardError, this, &StabilizeTask::processLogInfo); 0169 qDebug() << "=== STARTING PROCESS: " << producerArgs; 0170 m_jobProcess->start(KdenliveSettings::meltpath(), producerArgs); 0171 m_jobProcess->waitForFinished(-1); 0172 qDebug() << " + + + + + + + + SOURCE FILE PROCESSED: " << m_jobProcess->exitStatus(); 0173 bool result = m_jobProcess->exitStatus() == QProcess::NormalExit; 0174 m_progress = 100; 0175 QMetaObject::invokeMethod(m_object, "updateJobProgress"); 0176 if (m_isCanceled || !result) { 0177 if (!m_isCanceled) { 0178 QMetaObject::invokeMethod(pCore.get(), "displayBinLogMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("Failed to stabilize.")), 0179 Q_ARG(int, int(KMessageWidget::Warning)), Q_ARG(QString, m_logDetails)); 0180 } 0181 return; 0182 } 0183 QMetaObject::invokeMethod(pCore->bin(), "addProjectClipInFolder", Qt::QueuedConnection, Q_ARG(QString, m_destination), Q_ARG(QString, m_binId), 0184 Q_ARG(QString, folderId), Q_ARG(QString, QStringLiteral("stabilize"))); 0185 } 0186 0187 void StabilizeTask::processLogInfo() 0188 { 0189 const QString buffer = QString::fromUtf8(m_jobProcess->readAllStandardError()); 0190 m_logDetails.append(buffer); 0191 // Parse MLT output 0192 if (buffer.contains(QLatin1String("percentage:"))) { 0193 int progress = buffer.section(QStringLiteral("percentage:"), 1).simplified().section(QLatin1Char(' '), 0, 0).toInt(); 0194 if (progress == m_progress) { 0195 return; 0196 } 0197 m_progress = progress; 0198 QMetaObject::invokeMethod(m_object, "updateJobProgress"); 0199 } 0200 }