File indexing completed on 2024-05-05 04:54:09
0001 /* 0002 SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org> 0003 0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "previewmanager.h" 0008 #include "bin/projectitemmodel.h" 0009 #include "core.h" 0010 #include "dialogs/wizard.h" 0011 #include "doc/docundostack.hpp" 0012 #include "doc/kdenlivedoc.h" 0013 #include "kdenlivesettings.h" 0014 #include "mainwindow.h" 0015 #include "monitor/monitor.h" 0016 #include "profiles/profilemodel.hpp" 0017 #include "timeline2/view/timelinecontroller.h" 0018 #include "timeline2/view/timelinewidget.h" 0019 #include "xml/xml.hpp" 0020 0021 #include <KLocalizedString> 0022 #include <KMessageBox> 0023 #include <QCollator> 0024 #include <QCryptographicHash> 0025 #include <QMutexLocker> 0026 #include <QSaveFile> 0027 #include <QStandardPaths> 0028 0029 PreviewManager::PreviewManager(Mlt::Tractor *tractor, QUuid uuid, QObject *parent) 0030 : QObject(parent) 0031 , workingPreview(-1) 0032 , m_tractor(tractor) 0033 , m_uuid(uuid) 0034 , m_previewTrack(nullptr) 0035 , m_overlayTrack(nullptr) 0036 , m_warnOnCrash(true) 0037 , m_previewTrackIndex(-1) 0038 , m_initialized(false) 0039 { 0040 m_previewGatherTimer.setSingleShot(true); 0041 m_previewGatherTimer.setInterval(200); 0042 QObject::connect(&m_previewProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &PreviewManager::processEnded); 0043 0044 if (KdenliveSettings::kdenliverendererpath().isEmpty() || !QFileInfo::exists(KdenliveSettings::kdenliverendererpath())) { 0045 KdenliveSettings::setKdenliverendererpath(QString()); 0046 Wizard::fixKdenliveRenderPath(); 0047 if (KdenliveSettings::kdenliverendererpath().isEmpty()) { 0048 KMessageBox::error(QApplication::activeWindow(), 0049 i18n("Could not find the kdenlive_render application, something is wrong with your installation. Rendering will not work")); 0050 } 0051 } 0052 0053 connect(this, &PreviewManager::abortPreview, &m_previewProcess, &QProcess::kill, Qt::DirectConnection); 0054 connect(&m_previewProcess, &QProcess::readyReadStandardError, this, &PreviewManager::receivedStderr); 0055 } 0056 0057 PreviewManager::~PreviewManager() 0058 { 0059 if (m_initialized) { 0060 abortRendering(); 0061 if (m_undoDir.dirName() == QLatin1String("undo")) { 0062 m_undoDir.removeRecursively(); 0063 } 0064 if ((pCore->currentDoc()->url().isEmpty() && m_cacheDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot).isEmpty()) || 0065 m_cacheDir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty()) { 0066 if (m_cacheDir.dirName() == QLatin1String("preview")) { 0067 m_cacheDir.removeRecursively(); 0068 } 0069 } 0070 } 0071 delete m_overlayTrack; 0072 delete m_previewTrack; 0073 } 0074 0075 bool PreviewManager::initialize() 0076 { 0077 // Make sure our document id does not contain .. tricks 0078 bool ok; 0079 KdenliveDoc *doc = pCore->currentDoc(); 0080 QString documentId = QDir::cleanPath(doc->getDocumentProperty(QStringLiteral("documentid"))); 0081 documentId.toLongLong(&ok, 10); 0082 if (!ok || documentId.isEmpty()) { 0083 // Something is wrong, documentId should be a number (ms since epoch), abort 0084 pCore->displayMessage(i18n("Wrong document ID, cannot create temporary folder"), ErrorMessage); 0085 return false; 0086 } 0087 m_cacheDir = doc->getCacheDir(CachePreview, &ok, m_uuid); 0088 qDebug() << ":: GOT CACHE DIR: " << m_cacheDir.absolutePath(); 0089 if (!ok || !m_cacheDir.exists()) { 0090 pCore->displayMessage(i18n("Cannot read folder %1", m_cacheDir.absolutePath()), ErrorMessage); 0091 return false; 0092 } 0093 if (m_uuid == doc->uuid()) { 0094 if (m_cacheDir.dirName() != QLatin1String("preview") || m_cacheDir == QDir() || 0095 (!m_cacheDir.exists(QStringLiteral("undo")) && !m_cacheDir.mkdir(QStringLiteral("undo"))) || !m_cacheDir.absolutePath().contains(documentId)) { 0096 pCore->displayMessage(i18n("Something is wrong with cache folder %1", m_cacheDir.absolutePath()), ErrorMessage); 0097 return false; 0098 } 0099 } else { 0100 if (m_cacheDir.dirName().toLatin1() != QCryptographicHash::hash(m_uuid.toByteArray(), QCryptographicHash::Md5).toHex() || m_cacheDir == QDir() || 0101 (!m_cacheDir.exists(QStringLiteral("undo")) && !m_cacheDir.mkdir(QStringLiteral("undo"))) || !m_cacheDir.absolutePath().contains(documentId)) { 0102 pCore->displayMessage(i18n("Something is wrong with cache folder %1", m_cacheDir.absolutePath()), ErrorMessage); 0103 return false; 0104 } 0105 } 0106 if (!loadParams()) { 0107 pCore->displayMessage(i18n("Invalid timeline preview parameters"), ErrorMessage); 0108 return false; 0109 } 0110 m_undoDir = QDir(m_cacheDir.absoluteFilePath(QStringLiteral("undo"))); 0111 0112 // Make sure our cache dirs are inside the temporary folder 0113 if (!m_cacheDir.makeAbsolute() || !m_undoDir.makeAbsolute() || !m_undoDir.mkpath(QStringLiteral("."))) { 0114 pCore->displayMessage(i18n("Something is wrong with cache folders"), ErrorMessage); 0115 return false; 0116 } 0117 0118 connect(this, &PreviewManager::cleanupOldPreviews, this, &PreviewManager::doCleanupOldPreviews); 0119 connect(doc, &KdenliveDoc::removeInvalidUndo, this, &PreviewManager::slotRemoveInvalidUndo, Qt::DirectConnection); 0120 m_previewTimer.setSingleShot(true); 0121 m_previewTimer.setInterval(3000); 0122 connect(&m_previewTimer, &QTimer::timeout, this, &PreviewManager::startPreviewRender); 0123 connect(this, &PreviewManager::previewRender, this, &PreviewManager::gotPreviewRender, Qt::DirectConnection); 0124 connect(&m_previewGatherTimer, &QTimer::timeout, this, &PreviewManager::slotProcessDirtyChunks); 0125 m_initialized = true; 0126 return true; 0127 } 0128 0129 bool PreviewManager::buildPreviewTrack() 0130 { 0131 if (m_previewTrack != nullptr) { 0132 return false; 0133 } 0134 // Create overlay track 0135 qDebug() << "/// BUILDING PREVIEW TRACK\n----------------------\n----------------__"; 0136 m_previewTrack = new Mlt::Playlist(pCore->getProjectProfile()); 0137 m_previewTrack->set("kdenlive:playlistid", "timeline_preview"); 0138 m_tractor->lock(); 0139 reconnectTrack(); 0140 m_tractor->unlock(); 0141 return true; 0142 } 0143 0144 void PreviewManager::loadChunks(QVariantList previewChunks, QVariantList dirtyChunks, Mlt::Playlist &playlist) 0145 { 0146 if (previewChunks.isEmpty()) { 0147 previewChunks = m_renderedChunks; 0148 } 0149 if (dirtyChunks.isEmpty()) { 0150 dirtyChunks = m_dirtyChunks; 0151 } 0152 0153 QStringList existingChuncks; 0154 if (!previewChunks.isEmpty()) { 0155 existingChuncks = m_cacheDir.entryList(QDir::Files); 0156 } 0157 0158 int max = playlist.count(); 0159 std::shared_ptr<Mlt::Producer> clip; 0160 m_tractor->lock(); 0161 if (max == 0) { 0162 // Empty timeline preview, mark all as dirty 0163 for (auto &prev : previewChunks) { 0164 dirtyChunks << prev; 0165 } 0166 } 0167 for (int i = 0; i < max; i++) { 0168 if (playlist.is_blank(i)) { 0169 continue; 0170 } 0171 int position = playlist.clip_start(i); 0172 if (previewChunks.contains(QString::number(position))) { 0173 if (existingChuncks.contains(QString("%1.%2").arg(position).arg(m_extension))) { 0174 clip.reset(playlist.get_clip(i)); 0175 m_renderedChunks << position; 0176 m_previewTrack->insert_at(position, clip.get(), 1); 0177 } else { 0178 dirtyChunks << position; 0179 } 0180 } 0181 } 0182 m_previewTrack->consolidate_blanks(); 0183 m_tractor->unlock(); 0184 if (!dirtyChunks.isEmpty()) { 0185 std::sort(dirtyChunks.begin(), dirtyChunks.end(), chunkSort); 0186 QMutexLocker lock(&m_dirtyMutex); 0187 for (const auto &i : qAsConst(dirtyChunks)) { 0188 if (!m_dirtyChunks.contains(i)) { 0189 m_dirtyChunks << i; 0190 } 0191 } 0192 Q_EMIT dirtyChunksChanged(); 0193 } 0194 if (!previewChunks.isEmpty()) { 0195 Q_EMIT renderedChunksChanged(); 0196 } 0197 } 0198 0199 void PreviewManager::deletePreviewTrack() 0200 { 0201 m_tractor->lock(); 0202 disconnectTrack(); 0203 delete m_previewTrack; 0204 m_previewTrack = nullptr; 0205 m_dirtyChunks.clear(); 0206 m_renderedChunks.clear(); 0207 Q_EMIT dirtyChunksChanged(); 0208 Q_EMIT renderedChunksChanged(); 0209 m_tractor->unlock(); 0210 } 0211 0212 const QDir PreviewManager::getCacheDir() const 0213 { 0214 return m_cacheDir; 0215 } 0216 0217 void PreviewManager::reconnectTrack() 0218 { 0219 disconnectTrack(); 0220 if (!m_previewTrack && !m_overlayTrack) { 0221 m_previewTrackIndex = -1; 0222 return; 0223 } 0224 m_previewTrackIndex = m_tractor->count(); 0225 int increment = 0; 0226 if (m_previewTrack) { 0227 m_tractor->insert_track(*m_previewTrack, m_previewTrackIndex); 0228 std::shared_ptr<Mlt::Producer> tk(m_tractor->track(m_previewTrackIndex)); 0229 tk->set("hide", 2); 0230 // tk->set("kdenlive:playlistid", "timeline_preview"); 0231 increment++; 0232 } 0233 if (m_overlayTrack) { 0234 m_tractor->insert_track(*m_overlayTrack, m_previewTrackIndex + increment); 0235 std::shared_ptr<Mlt::Producer> tk(m_tractor->track(m_previewTrackIndex + increment)); 0236 tk->set("hide", 2); 0237 // tk->set("kdenlive:playlistid", "timeline_overlay"); 0238 } 0239 } 0240 0241 void PreviewManager::disconnectTrack() 0242 { 0243 if (m_previewTrackIndex > -1) { 0244 Mlt::Producer *prod = m_tractor->track(m_previewTrackIndex); 0245 if (strcmp(prod->get("kdenlive:playlistid"), "timeline_preview") == 0 || strcmp(prod->get("kdenlive:playlistid"), "timeline_overlay") == 0) { 0246 m_tractor->remove_track(m_previewTrackIndex); 0247 } 0248 delete prod; 0249 if (m_tractor->count() == m_previewTrackIndex + 1) { 0250 // overlay track still here, remove 0251 Mlt::Producer *trkprod = m_tractor->track(m_previewTrackIndex); 0252 if (strcmp(trkprod->get("kdenlive:playlistid"), "timeline_overlay") == 0) { 0253 m_tractor->remove_track(m_previewTrackIndex); 0254 } 0255 delete trkprod; 0256 } 0257 } 0258 m_previewTrackIndex = -1; 0259 } 0260 0261 void PreviewManager::disable() 0262 { 0263 if (m_previewTrackIndex > -1) { 0264 if (m_previewTrack) { 0265 m_previewTrack->set("hide", 3); 0266 } 0267 if (m_overlayTrack) { 0268 m_overlayTrack->set("hide", 3); 0269 } 0270 } 0271 } 0272 0273 void PreviewManager::enable() 0274 { 0275 if (m_previewTrackIndex > -1) { 0276 if (m_previewTrack) { 0277 m_previewTrack->set("hide", 2); 0278 } 0279 if (m_overlayTrack) { 0280 m_overlayTrack->set("hide", 2); 0281 } 0282 } 0283 } 0284 0285 bool PreviewManager::loadParams() 0286 { 0287 KdenliveDoc *doc = pCore->currentDoc(); 0288 m_extension = doc->getDocumentProperty(QStringLiteral("previewextension")); 0289 m_consumerParams = doc->getDocumentProperty(QStringLiteral("previewparameters")).split(QLatin1Char(' '), Qt::SkipEmptyParts); 0290 0291 if (m_consumerParams.isEmpty() || m_extension.isEmpty()) { 0292 doc->selectPreviewProfile(); 0293 m_consumerParams = doc->getDocumentProperty(QStringLiteral("previewparameters")).split(QLatin1Char(' '), Qt::SkipEmptyParts); 0294 m_extension = doc->getDocumentProperty(QStringLiteral("previewextension")); 0295 } 0296 if (m_consumerParams.isEmpty() || m_extension.isEmpty()) { 0297 return false; 0298 } 0299 // Remove the r= and s= parameter (forcing framerate / frame size) as it causes rendering failure. 0300 // These parameters should be provided by MLT's profile 0301 // NOTE: this is still required for DNxHD so leave it 0302 /*for (int i = 0; i < m_consumerParams.count(); i++) { 0303 if (m_consumerParams.at(i).startsWith(QStringLiteral("r=")) || m_consumerParams.at(i).startsWith(QStringLiteral("s="))) { 0304 m_consumerParams.removeAt(i); 0305 i--; 0306 } 0307 }*/ 0308 if (doc->getDocumentProperty(QStringLiteral("resizepreview")).toInt() != 0) { 0309 int resizeWidth = doc->getDocumentProperty(QStringLiteral("previewheight")).toInt(); 0310 m_consumerParams << QStringLiteral("s=%1x%2").arg(int(resizeWidth * pCore->getCurrentDar())).arg(resizeWidth); 0311 } 0312 m_consumerParams << QStringLiteral("an=1"); 0313 if (KdenliveSettings::gpu_accel()) { 0314 m_consumerParams << QStringLiteral("glsl.=1"); 0315 } 0316 return true; 0317 } 0318 0319 void PreviewManager::invalidatePreviews() 0320 { 0321 QMutexLocker lock(&m_previewMutex); 0322 bool timer = KdenliveSettings::autopreview(); 0323 if (m_previewTimer.isActive()) { 0324 m_previewTimer.stop(); 0325 timer = true; 0326 } 0327 KdenliveDoc *doc = pCore->currentDoc(); 0328 int stackIx = doc->commandStack()->index(); 0329 int stackMax = doc->commandStack()->count(); 0330 if (stackIx == stackMax && !m_undoDir.exists(QString::number(stackIx - 1))) { 0331 // We just added a new command in stack, archive existing chunks 0332 int ix = stackIx - 1; 0333 m_undoDir.mkdir(QString::number(ix)); 0334 bool foundPreviews = false; 0335 for (const auto &i : m_dirtyChunks) { 0336 QString current = QStringLiteral("%1.%2").arg(i.toInt()).arg(m_extension); 0337 if (m_cacheDir.rename(current, QStringLiteral("undo/%1/%2").arg(ix).arg(current))) { 0338 foundPreviews = true; 0339 } 0340 } 0341 if (!foundPreviews) { 0342 // No preview files found, remove undo folder 0343 m_undoDir.rmdir(QString::number(ix)); 0344 } else { 0345 // new chunks archived, cleanup old ones 0346 Q_EMIT cleanupOldPreviews(); 0347 } 0348 } else { 0349 // Restore existing chunks, delete others 0350 // Check if we just undo the last stack action, then backup, otherwise delete 0351 bool lastUndo = false; 0352 if (stackIx + 1 == stackMax) { 0353 if (!m_undoDir.exists(QString::number(stackMax))) { 0354 lastUndo = true; 0355 bool foundPreviews = false; 0356 m_undoDir.mkdir(QString::number(stackMax)); 0357 for (const auto &i : m_dirtyChunks) { 0358 QString current = QStringLiteral("%1.%2").arg(i.toInt()).arg(m_extension); 0359 if (m_cacheDir.rename(current, QStringLiteral("undo/%1/%2").arg(stackMax).arg(current))) { 0360 foundPreviews = true; 0361 } 0362 } 0363 if (!foundPreviews) { 0364 m_undoDir.rmdir(QString::number(stackMax)); 0365 } 0366 } 0367 } 0368 bool moveFile = true; 0369 QDir tmpDir = m_undoDir; 0370 if (!tmpDir.cd(QString::number(stackIx))) { 0371 moveFile = false; 0372 } 0373 QVariantList foundChunks; 0374 for (const auto &i : m_dirtyChunks) { 0375 QString cacheFileName = QStringLiteral("%1.%2").arg(i.toInt()).arg(m_extension); 0376 if (!lastUndo) { 0377 m_cacheDir.remove(cacheFileName); 0378 } 0379 if (moveFile) { 0380 if (QFile::copy(tmpDir.absoluteFilePath(cacheFileName), m_cacheDir.absoluteFilePath(cacheFileName))) { 0381 foundChunks << i; 0382 } else { 0383 qDebug() << "// ERROR PROCESSE CHUNK: " << i << ", " << cacheFileName; 0384 } 0385 } 0386 } 0387 if (!foundChunks.isEmpty()) { 0388 std::sort(foundChunks.begin(), foundChunks.end(), chunkSort); 0389 m_dirtyMutex.lock(); 0390 for (auto &ck : foundChunks) { 0391 m_dirtyChunks.removeAll(ck); 0392 m_renderedChunks << ck; 0393 } 0394 m_dirtyMutex.unlock(); 0395 Q_EMIT dirtyChunksChanged(); 0396 Q_EMIT renderedChunksChanged(); 0397 reloadChunks(foundChunks); 0398 } 0399 } 0400 doc->setModified(true); 0401 if (timer) { 0402 m_previewTimer.start(); 0403 } 0404 } 0405 0406 void PreviewManager::doCleanupOldPreviews() 0407 { 0408 if (m_undoDir.dirName() != QLatin1String("undo")) { 0409 return; 0410 } 0411 QStringList dirs = m_undoDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); 0412 0413 // Use QCollator to do a natural sorting so that 10 is after 2 0414 QCollator collator; 0415 collator.setNumericMode(true); 0416 std::sort(dirs.begin(), dirs.end(), [&collator](const QString &file1, const QString &file2) { return collator.compare(file1, file2) < 0; }); 0417 bool ok; 0418 while (dirs.count() > 5) { 0419 QDir tmp = m_undoDir; 0420 QString dirName = dirs.takeFirst(); 0421 dirName.toInt(&ok); 0422 if (ok && tmp.cd(dirName)) { 0423 tmp.removeRecursively(); 0424 } 0425 } 0426 } 0427 0428 void PreviewManager::clearPreviewRange(bool resetZones) 0429 { 0430 m_previewGatherTimer.stop(); 0431 abortRendering(); 0432 m_tractor->lock(); 0433 bool hasPreview = m_previewTrack != nullptr; 0434 QMutexLocker lock(&m_dirtyMutex); 0435 for (const auto &ix : qAsConst(m_renderedChunks)) { 0436 m_cacheDir.remove(QStringLiteral("%1.%2").arg(ix.toInt()).arg(m_extension)); 0437 if (!m_dirtyChunks.contains(ix)) { 0438 m_dirtyChunks << ix; 0439 } 0440 if (!hasPreview) { 0441 continue; 0442 } 0443 int trackIx = m_previewTrack->get_clip_index_at(ix.toInt()); 0444 if (!m_previewTrack->is_blank(trackIx)) { 0445 Mlt::Producer *prod = m_previewTrack->replace_with_blank(trackIx); 0446 delete prod; 0447 } 0448 } 0449 if (hasPreview) { 0450 m_previewTrack->consolidate_blanks(); 0451 } 0452 m_tractor->unlock(); 0453 m_renderedChunks.clear(); 0454 // Reload preview params 0455 loadParams(); 0456 if (resetZones) { 0457 m_dirtyChunks.clear(); 0458 } 0459 Q_EMIT renderedChunksChanged(); 0460 Q_EMIT dirtyChunksChanged(); 0461 } 0462 0463 void PreviewManager::addPreviewRange(const QPoint zone, bool add) 0464 { 0465 int chunkSize = KdenliveSettings::timelinechunks(); 0466 int startChunk = zone.x() / chunkSize; 0467 int endChunk = int(rintl(zone.y() / chunkSize)); 0468 QList<int> toRemove; 0469 QMutexLocker lock(&m_dirtyMutex); 0470 for (int i = startChunk; i <= endChunk; i++) { 0471 int frame = i * chunkSize; 0472 if (add) { 0473 if (!m_renderedChunks.contains(frame) && !m_dirtyChunks.contains(frame)) { 0474 m_dirtyChunks << frame; 0475 } 0476 } else { 0477 if (m_renderedChunks.contains(frame)) { 0478 toRemove << frame; 0479 m_renderedChunks.removeAll(frame); 0480 } else { 0481 m_dirtyChunks.removeAll(frame); 0482 } 0483 } 0484 } 0485 if (add) { 0486 Q_EMIT dirtyChunksChanged(); 0487 if (m_previewProcess.state() == QProcess::NotRunning && KdenliveSettings::autopreview()) { 0488 m_previewTimer.start(); 0489 } 0490 } else { 0491 // Remove processed chunks 0492 bool isRendering = m_previewProcess.state() != QProcess::NotRunning; 0493 m_previewGatherTimer.stop(); 0494 abortRendering(); 0495 m_tractor->lock(); 0496 bool hasPreview = m_previewTrack != nullptr; 0497 for (int ix : qAsConst(toRemove)) { 0498 m_cacheDir.remove(QStringLiteral("%1.%2").arg(ix).arg(m_extension)); 0499 if (!hasPreview) { 0500 continue; 0501 } 0502 int trackIx = m_previewTrack->get_clip_index_at(ix); 0503 if (!m_previewTrack->is_blank(trackIx)) { 0504 Mlt::Producer *prod = m_previewTrack->replace_with_blank(trackIx); 0505 delete prod; 0506 } 0507 } 0508 if (hasPreview) { 0509 m_previewTrack->consolidate_blanks(); 0510 } 0511 Q_EMIT renderedChunksChanged(); 0512 Q_EMIT dirtyChunksChanged(); 0513 m_tractor->unlock(); 0514 if (isRendering || KdenliveSettings::autopreview()) { 0515 m_previewTimer.start(); 0516 } 0517 } 0518 } 0519 0520 void PreviewManager::abortRendering() 0521 { 0522 if (m_previewProcess.state() == QProcess::NotRunning) { 0523 return; 0524 } 0525 // Don't display error message on voluntary abort 0526 m_warnOnCrash = false; 0527 Q_EMIT abortPreview(); 0528 m_previewProcess.waitForFinished(); 0529 if (m_previewProcess.state() != QProcess::NotRunning) { 0530 m_previewProcess.kill(); 0531 m_previewProcess.waitForFinished(); 0532 } 0533 // Re-init time estimation 0534 Q_EMIT previewRender(-1, QString(), 1000); 0535 } 0536 0537 bool PreviewManager::hasDefinedRange() const 0538 { 0539 return (!m_renderedChunks.isEmpty() || !m_dirtyChunks.isEmpty()); 0540 } 0541 0542 void PreviewManager::startPreviewRender() 0543 { 0544 QMutexLocker lock(&m_previewMutex); 0545 if (!m_dirtyChunks.isEmpty()) { 0546 // Abort any rendering 0547 abortRendering(); 0548 m_waitingThumbs.clear(); 0549 // clear log 0550 m_errorLog.clear(); 0551 const QString sceneList = m_cacheDir.absoluteFilePath(QStringLiteral("preview.mlt")); 0552 if (!KdenliveSettings::proxypreview() && pCore->currentDoc()->useProxy()) { 0553 const QString playlist = 0554 pCore->projectItemModel()->sceneList(m_cacheDir.absolutePath(), QString(), QString(), pCore->currentDoc()->getTimeline(m_uuid)->tractor(), -1); 0555 QDomDocument doc; 0556 doc.setContent(playlist); 0557 KdenliveDoc::useOriginals(doc); 0558 if (!Xml::docContentToFile(doc, sceneList)) { 0559 return; 0560 } 0561 } else { 0562 pCore->currentDoc()->getTimeline(m_uuid)->sceneList(m_cacheDir.absolutePath(), sceneList); 0563 } 0564 m_previewTimer.stop(); 0565 doPreviewRender(sceneList); 0566 } 0567 } 0568 0569 void PreviewManager::receivedStderr() 0570 { 0571 QStringList resultList = QString::fromLocal8Bit(m_previewProcess.readAllStandardError()).split(QLatin1Char('\n'), Qt::SkipEmptyParts); 0572 for (auto &result : resultList) { 0573 if (result.startsWith(QLatin1String("START:"))) { 0574 if (m_previewProcess.state() == QProcess::Running) { 0575 workingPreview = result.section(QLatin1String("START:"), 1).simplified().toInt(); 0576 Q_EMIT workingPreviewChanged(); 0577 } 0578 } else if (result.startsWith(QLatin1String("DONE:"))) { 0579 int chunk = result.section(QLatin1String("DONE:"), 1).simplified().toInt(); 0580 m_processedChunks++; 0581 QString fileName = QStringLiteral("%1.%2").arg(chunk).arg(m_extension); 0582 Q_EMIT previewRender(chunk, m_cacheDir.absoluteFilePath(fileName), 1000 * m_processedChunks / m_chunksToRender); 0583 } else { 0584 m_errorLog.append(result); 0585 } 0586 } 0587 } 0588 0589 void PreviewManager::doPreviewRender(const QString &scene) 0590 { 0591 // initialize progress bar 0592 if (m_dirtyChunks.isEmpty()) { 0593 return; 0594 } 0595 QMutexLocker lock(&m_dirtyMutex); 0596 Q_ASSERT(m_previewProcess.state() == QProcess::NotRunning); 0597 std::sort(m_dirtyChunks.begin(), m_dirtyChunks.end(), chunkSort); 0598 const QStringList dirtyChunks = getCompressedList(m_dirtyChunks); 0599 m_chunksToRender = m_dirtyChunks.count(); 0600 m_processedChunks = 0; 0601 int chunkSize = KdenliveSettings::timelinechunks(); 0602 QStringList args{QStringLiteral("preview-chunks"), 0603 scene, 0604 m_cacheDir.absolutePath(), 0605 dirtyChunks.join(QLatin1Char(',')), 0606 QString::number(chunkSize - 1), 0607 pCore->getCurrentProfilePath(), 0608 m_extension, 0609 m_consumerParams.join(QLatin1Char(' '))}; 0610 pCore->currentDoc()->previewProgress(0); 0611 m_previewProcess.start(KdenliveSettings::kdenliverendererpath(), args); 0612 if (m_previewProcess.waitForStarted()) { 0613 qDebug() << " - - -STARTING PREVIEW JOBS . . . STARTED: " << args; 0614 } 0615 } 0616 0617 void PreviewManager::processEnded(int exitCode, QProcess::ExitStatus status) 0618 { 0619 const QString sceneList = m_cacheDir.absoluteFilePath(QStringLiteral("preview.mlt")); 0620 QFile::remove(sceneList); 0621 if (pCore->window() && (status == QProcess::QProcess::CrashExit || exitCode != 0)) { 0622 Q_EMIT previewRender(0, m_errorLog, -1); 0623 if (workingPreview >= 0) { 0624 const QString fileName = QStringLiteral("%1.%2").arg(workingPreview).arg(m_extension); 0625 if (m_cacheDir.exists(fileName)) { 0626 m_cacheDir.remove(fileName); 0627 } 0628 } 0629 } else { 0630 // Normal exit and exit code 0: everything okay 0631 pCore->currentDoc()->previewProgress(1000); 0632 } 0633 workingPreview = -1; 0634 m_warnOnCrash = true; 0635 Q_EMIT workingPreviewChanged(); 0636 } 0637 0638 void PreviewManager::slotProcessDirtyChunks() 0639 { 0640 if (m_dirtyChunks.isEmpty()) { 0641 return; 0642 } 0643 invalidatePreviews(); 0644 if (KdenliveSettings::autopreview()) { 0645 m_previewTimer.start(); 0646 } 0647 } 0648 0649 void PreviewManager::slotRemoveInvalidUndo(int ix) 0650 { 0651 QMutexLocker lock(&m_previewMutex); 0652 if (m_undoDir.dirName() != QLatin1String("undo")) { 0653 // Make sure we delete correct folder 0654 return; 0655 } 0656 QStringList dirs = m_undoDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); 0657 bool ok; 0658 for (const QString &dir : qAsConst(dirs)) { 0659 if (dir.toInt(&ok) >= ix && ok) { 0660 QDir tmp = m_undoDir; 0661 if (tmp.cd(dir)) { 0662 tmp.removeRecursively(); 0663 } 0664 } 0665 } 0666 } 0667 0668 void PreviewManager::invalidatePreview(int startFrame, int endFrame) 0669 { 0670 if (m_previewTrack == nullptr) { 0671 return; 0672 } 0673 int chunkSize = KdenliveSettings::timelinechunks(); 0674 int start = startFrame - startFrame % chunkSize; 0675 int end = endFrame - endFrame % chunkSize; 0676 0677 m_previewGatherTimer.stop(); 0678 bool previewWasRunning = m_previewProcess.state() == QProcess::Running; 0679 bool alreadyRendered = false; 0680 bool wasInDirtyZone = false; 0681 if (!m_renderedChunks.isEmpty()) { 0682 // Check if the invalidated zone was already rendered 0683 std::sort(m_renderedChunks.begin(), m_renderedChunks.end(), chunkSort); 0684 if (start <= m_renderedChunks.last().toInt() && end >= m_renderedChunks.first().toInt()) { 0685 alreadyRendered = true; 0686 } else if (workingPreview >= start && workingPreview <= end) { 0687 alreadyRendered = true; 0688 } 0689 } 0690 if (!alreadyRendered && !m_dirtyChunks.isEmpty()) { 0691 // Check if the invalidate zone is in the current todo list (dirtychunks) 0692 std::sort(m_dirtyChunks.begin(), m_dirtyChunks.end(), chunkSort); 0693 if (start <= m_dirtyChunks.last().toInt() && end >= m_dirtyChunks.first().toInt()) { 0694 wasInDirtyZone = true; 0695 } 0696 } 0697 if (alreadyRendered) { 0698 if (previewWasRunning) { 0699 abortRendering(); 0700 } 0701 m_tractor->lock(); 0702 bool chunksChanged = false; 0703 for (int i = start; i <= end; i += chunkSize) { 0704 if (m_renderedChunks.contains(i)) { 0705 int ix = m_previewTrack->get_clip_index_at(i); 0706 if (m_previewTrack->is_blank(ix)) { 0707 continue; 0708 } 0709 Mlt::Producer *prod = m_previewTrack->replace_with_blank(ix); 0710 delete prod; 0711 QVariant val(i); 0712 m_renderedChunks.removeAll(val); 0713 if (!m_dirtyChunks.contains(val)) { 0714 QMutexLocker lock(&m_dirtyMutex); 0715 m_dirtyChunks << val; 0716 chunksChanged = true; 0717 } 0718 } 0719 } 0720 m_tractor->unlock(); 0721 if (chunksChanged) { 0722 m_previewTrack->consolidate_blanks(); 0723 Q_EMIT renderedChunksChanged(); 0724 Q_EMIT dirtyChunksChanged(); 0725 } 0726 } else if (wasInDirtyZone) { 0727 // Abort rendering, playlist needs to be recreated 0728 if (previewWasRunning) { 0729 abortRendering(); 0730 } 0731 } else { 0732 // Invalidated zone outside our rendered zones 0733 return; 0734 } 0735 m_previewGatherTimer.start(); 0736 } 0737 0738 void PreviewManager::reloadChunks(const QVariantList &chunks) 0739 { 0740 if (m_previewTrack == nullptr || chunks.isEmpty()) { 0741 return; 0742 } 0743 m_tractor->lock(); 0744 for (const auto &ix : chunks) { 0745 if (m_previewTrack->is_blank_at(ix.toInt())) { 0746 QString fileName = m_cacheDir.absoluteFilePath(QStringLiteral("%1.%2").arg(ix.toInt()).arg(m_extension)); 0747 fileName.prepend(QStringLiteral("avformat:")); 0748 Mlt::Producer prod(pCore->getProjectProfile(), fileName.toUtf8().constData()); 0749 if (prod.is_valid()) { 0750 // m_ruler->updatePreview(ix, true); 0751 prod.set("mlt_service", "avformat-novalidate"); 0752 m_previewTrack->insert_at(ix.toInt(), &prod, 1); 0753 } 0754 } 0755 } 0756 m_previewTrack->consolidate_blanks(); 0757 m_tractor->unlock(); 0758 } 0759 0760 void PreviewManager::gotPreviewRender(int frame, const QString &file, int progress) 0761 { 0762 if (m_previewTrack == nullptr) { 0763 return; 0764 } 0765 if (frame < 0) { 0766 pCore->currentDoc()->previewProgress(1000); 0767 return; 0768 } 0769 if (file.isEmpty() || progress < 0) { 0770 pCore->currentDoc()->previewProgress(progress); 0771 if (progress < 0) { 0772 if (m_warnOnCrash) { 0773 pCore->displayMessage(i18n("Preview rendering failed, check your parameters. %1Show details...%2", 0774 QString("<a href=\"" + QString::fromLatin1(QUrl::toPercentEncoding(file)) + QStringLiteral("\">")), 0775 QStringLiteral("</a>")), 0776 MltError); 0777 } else { 0778 // TODO display info about stopped preview job 0779 } 0780 } 0781 return; 0782 } 0783 if (m_previewTrack->is_blank_at(frame)) { 0784 Mlt::Producer prod(pCore->getProjectProfile(), QString("avformat:%1").arg(file).toUtf8().constData()); 0785 if (prod.is_valid() && prod.get_length() == KdenliveSettings::timelinechunks()) { 0786 m_dirtyMutex.lock(); 0787 m_dirtyChunks.removeAll(QVariant(frame)); 0788 m_dirtyMutex.unlock(); 0789 m_renderedChunks << frame; 0790 Q_EMIT renderedChunksChanged(); 0791 prod.set("mlt_service", "avformat-novalidate"); 0792 m_tractor->lock(); 0793 m_previewTrack->insert_at(frame, &prod, 1); 0794 m_previewTrack->consolidate_blanks(); 0795 m_tractor->unlock(); 0796 pCore->currentDoc()->previewProgress(progress); 0797 pCore->currentDoc()->setModified(true); 0798 } else { 0799 qCDebug(KDENLIVE_LOG) << "* * * INVALID PROD: " << file; 0800 corruptedChunk(frame, file); 0801 } 0802 } else { 0803 qCDebug(KDENLIVE_LOG) << "* * * NON EMPTY PROD: " << frame; 0804 } 0805 } 0806 0807 void PreviewManager::corruptedChunk(int frame, const QString &fileName) 0808 { 0809 Q_EMIT abortPreview(); 0810 m_previewProcess.waitForFinished(); 0811 if (workingPreview >= 0) { 0812 workingPreview = -1; 0813 Q_EMIT workingPreviewChanged(); 0814 } 0815 Q_EMIT previewRender(0, m_errorLog, -1); 0816 m_cacheDir.remove(fileName); 0817 if (!m_dirtyChunks.contains(frame)) { 0818 QMutexLocker lock(&m_dirtyMutex); 0819 m_dirtyChunks << frame; 0820 std::sort(m_dirtyChunks.begin(), m_dirtyChunks.end(), chunkSort); 0821 } 0822 } 0823 0824 int PreviewManager::setOverlayTrack(Mlt::Playlist *overlay) 0825 { 0826 m_overlayTrack = overlay; 0827 m_overlayTrack->set("kdenlive:playlistid", "timeline_overlay"); 0828 reconnectTrack(); 0829 return m_previewTrackIndex; 0830 } 0831 0832 void PreviewManager::removeOverlayTrack() 0833 { 0834 delete m_overlayTrack; 0835 m_overlayTrack = nullptr; 0836 reconnectTrack(); 0837 } 0838 0839 QPair<QStringList, QStringList> PreviewManager::previewChunks() 0840 { 0841 QMutexLocker lock(&m_dirtyMutex); 0842 std::sort(m_renderedChunks.begin(), m_renderedChunks.end(), chunkSort); 0843 const QStringList renderedChunks = getCompressedList(m_renderedChunks); 0844 std::sort(m_dirtyChunks.begin(), m_dirtyChunks.end(), chunkSort); 0845 const QStringList dirtyChunks = getCompressedList(m_dirtyChunks); 0846 lock.unlock(); 0847 return {renderedChunks, dirtyChunks}; 0848 } 0849 0850 const QStringList PreviewManager::getCompressedList(const QVariantList items) const 0851 { 0852 QStringList resultString; 0853 int lastFrame = -1; 0854 QString currentString; 0855 for (const QVariant &frame : items) { 0856 int current = frame.toInt(); 0857 if (current - KdenliveSettings::timelinechunks() == lastFrame) { 0858 lastFrame = current; 0859 if (frame == items.last()) { 0860 currentString.append(QString("-%1").arg(lastFrame)); 0861 resultString << currentString; 0862 currentString.clear(); 0863 } 0864 continue; 0865 } 0866 if (currentString.isEmpty()) { 0867 currentString = frame.toString(); 0868 } else if (currentString == QString::number(lastFrame)) { 0869 // Only one chunk, store it 0870 resultString << currentString; 0871 currentString = frame.toString(); 0872 } else { 0873 // Range, store 0874 currentString.append(QString("-%1").arg(lastFrame)); 0875 resultString << currentString; 0876 currentString = frame.toString(); 0877 } 0878 lastFrame = current; 0879 } 0880 if (!currentString.isEmpty()) { 0881 resultString << currentString; 0882 } 0883 return resultString; 0884 } 0885 0886 bool PreviewManager::hasOverlayTrack() const 0887 { 0888 return m_overlayTrack != nullptr; 0889 } 0890 0891 bool PreviewManager::hasPreviewTrack() const 0892 { 0893 return m_previewTrack != nullptr; 0894 } 0895 0896 int PreviewManager::addedTracks() const 0897 { 0898 if (m_previewTrack) { 0899 if (m_overlayTrack) { 0900 return 2; 0901 } 0902 return 1; 0903 } else if (m_overlayTrack) { 0904 return 1; 0905 } 0906 return -1; 0907 } 0908 0909 bool PreviewManager::isRunning() const 0910 { 0911 return workingPreview >= 0 || m_previewProcess.state() != QProcess::NotRunning; 0912 }