File indexing completed on 2024-04-21 04:51:39

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 }