diff --git a/src/assets/view/widgets/listparamwidget.cpp b/src/assets/view/widgets/listparamwidget.cpp index 2fa6a6567..c7988efec 100644 --- a/src/assets/view/widgets/listparamwidget.cpp +++ b/src/assets/view/widgets/listparamwidget.cpp @@ -1,144 +1,150 @@ /*************************************************************************** * Copyright (C) 2016 by Nicolas Carion * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) version 3 or any later version accepted by the * * membership of KDE e.V. (or its successor approved by the membership * * of KDE e.V.), which shall act as a proxy defined in Section 14 of * * version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "listparamwidget.h" #include "assets/model/assetparametermodel.hpp" #include "core.h" #include "mainwindow.h" ListParamWidget::ListParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) { setupUi(this); // Get data from model QString name = m_model->data(m_index, AssetParameterModel::NameRole).toString(); QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString(); // setup the comment setToolTip(comment); m_labelComment->setText(comment); m_widgetComment->setHidden(true); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); m_list->setIconSize(QSize(50, 30)); // setup the name m_labelName->setText(m_model->data(m_index, Qt::DisplayRole).toString()); slotRefresh(); QLocale locale; // emit the signal of the base class when appropriate // The connection is ugly because the signal "currentIndexChanged" is overloaded in QComboBox connect(this->m_list, static_cast(&QComboBox::currentIndexChanged), [this](int) { emit valueChanged(m_index, m_list->itemData(m_list->currentIndex()).toString(), true); }); } void ListParamWidget::setCurrentIndex(int index) { m_list->setCurrentIndex(index); } void ListParamWidget::setCurrentText(const QString &text) { m_list->setCurrentText(text); } void ListParamWidget::addItem(const QString &text, const QVariant &value) { m_list->addItem(text, value); } void ListParamWidget::setItemIcon(int index, const QIcon &icon) { m_list->setItemIcon(index, icon); } void ListParamWidget::setIconSize(const QSize &size) { m_list->setIconSize(size); } void ListParamWidget::slotShowComment(bool show) { if (!m_labelComment->text().isEmpty()) { m_widgetComment->setVisible(show); } } QString ListParamWidget::getValue() { return m_list->currentData().toString(); } void ListParamWidget::slotRefresh() { const QSignalBlocker bk(m_list); m_list->clear(); QStringList names = m_model->data(m_index, AssetParameterModel::ListNamesRole).toStringList(); QStringList values = m_model->data(m_index, AssetParameterModel::ListValuesRole).toStringList(); QString value = m_model->data(m_index, AssetParameterModel::ValueRole).toString(); if (values.first() == QLatin1String("%lumaPaths")) { // Special case: Luma files // Create thumbnails if (pCore->getCurrentFrameSize().width() > 1000) { // HD project - values = MainWindow::m_lumaFiles.value(QStringLiteral("HD")); + values = MainWindow::m_lumaFiles.value(QStringLiteral("16_9")); + } else if (pCore->getCurrentFrameSize().height() > 1000) { + values = MainWindow::m_lumaFiles.value(QStringLiteral("9_16")); + } else if (pCore->getCurrentFrameSize().height() == pCore->getCurrentFrameSize().width()) { + values = MainWindow::m_lumaFiles.value(QStringLiteral("square")); + } else if (pCore->getCurrentFrameSize().height() == 480) { + values = MainWindow::m_lumaFiles.value(QStringLiteral("NTSC")); } else { values = MainWindow::m_lumaFiles.value(QStringLiteral("PAL")); } m_list->addItem(i18n("None (Dissolve)")); for (int j = 0; j < values.count(); ++j) { const QString &entry = values.at(j); m_list->addItem(values.at(j).section(QLatin1Char('/'), -1), entry); if (!entry.isEmpty() && (entry.endsWith(QLatin1String(".png")) || entry.endsWith(QLatin1String(".pgm")))) { if (!MainWindow::m_lumacache.contains(entry)) { QImage pix(entry); MainWindow::m_lumacache.insert(entry, pix.scaled(50, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation)); } m_list->setItemIcon(j + 1, QPixmap::fromImage(MainWindow::m_lumacache.value(entry))); } } if (!value.isEmpty() && values.contains(value)) { m_list->setCurrentIndex(values.indexOf(value) + 1); } } else { if (names.count() != values.count()) { names = values; } QLocale locale; for (int i = 0; i < names.count(); i++) { QString val = values.at(i); bool ok; double num = val.toDouble(&ok); if (ok) { val = locale.toString(num); } m_list->addItem(names.at(i), val); } if (!value.isEmpty()) { int ix = m_list->findData(value); if (ix > -1) { m_list->setCurrentIndex(ix); } } } } diff --git a/src/doc/documentchecker.cpp b/src/doc/documentchecker.cpp index 33e64a5e2..ed1741f15 100644 --- a/src/doc/documentchecker.cpp +++ b/src/doc/documentchecker.cpp @@ -1,1288 +1,1302 @@ /*************************************************************************** * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "documentchecker.h" #include "bin/binplaylist.hpp" #include "effects/effectsrepository.hpp" #include "kdenlivesettings.h" #include "kthumb.h" #include "titler/titlewidget.h" #include #include #include #include #include "kdenlive_debug.h" #include #include #include #include #include #include #include const int hashRole = Qt::UserRole; const int sizeRole = Qt::UserRole + 1; const int idRole = Qt::UserRole + 2; const int statusRole = Qt::UserRole + 3; const int typeRole = Qt::UserRole + 4; const int typeOriginalResource = Qt::UserRole + 5; const int clipTypeRole = Qt::UserRole + 6; const int CLIPMISSING = 0; const int CLIPOK = 1; const int CLIPPLACEHOLDER = 2; const int PROXYMISSING = 4; const int SOURCEMISSING = 5; const int LUMAMISSING = 10; const int LUMAOK = 11; const int LUMAPLACEHOLDER = 12; enum MISSINGTYPE { TITLE_IMAGE_ELEMENT = 20, TITLE_FONT_ELEMENT = 21 }; DocumentChecker::DocumentChecker(QUrl url, const QDomDocument &doc) : m_url(std::move(url)) , m_doc(doc) , m_dialog(nullptr) { } QMap DocumentChecker::getLumaPairs() const { QMap lumaSearchPairs; lumaSearchPairs.insert(QStringLiteral("luma"), QStringLiteral("resource")); lumaSearchPairs.insert(QStringLiteral("movit.luma_mix"), QStringLiteral("resource")); lumaSearchPairs.insert(QStringLiteral("composite"), QStringLiteral("luma")); lumaSearchPairs.insert(QStringLiteral("region"), QStringLiteral("composite.luma")); return lumaSearchPairs; } bool DocumentChecker::hasErrorInClips() { int max; QDomElement baseElement = m_doc.documentElement(); QString root = baseElement.attribute(QStringLiteral("root")); if (!root.isEmpty()) { QDir dir(root); if (!dir.exists()) { // Looks like project was moved, try recovering root from current project url m_rootReplacement.first = dir.absolutePath() + QDir::separator(); root = m_url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(); baseElement.setAttribute(QStringLiteral("root"), root); root = QDir::cleanPath(root) + QDir::separator(); m_rootReplacement.second = root; } else { root = QDir::cleanPath(root) + QDir::separator(); } } // Check if strorage folder for temp files exists QString storageFolder; QDir projectDir(m_url.adjusted(QUrl::RemoveFilename).toLocalFile()); QString documentid; QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist")); for (int i = 0; i < playlists.count(); ++i) { if (playlists.at(i).toElement().attribute(QStringLiteral("id")) == BinPlaylist::binPlaylistId) { documentid = Xml::getXmlProperty(playlists.at(i).toElement(), QStringLiteral("kdenlive:docproperties.documentid")); if (documentid.isEmpty()) { // invalid document id, recreate one documentid = QString::number(QDateTime::currentMSecsSinceEpoch()); // TODO: Warn on invalid doc id Xml::setXmlProperty(playlists.at(i).toElement(), QStringLiteral("kdenlive:docproperties.documentid"), documentid); } storageFolder = Xml::getXmlProperty(playlists.at(i).toElement(), QStringLiteral("kdenlive:docproperties.storagefolder")); if (!storageFolder.isEmpty() && QFileInfo(storageFolder).isRelative()) { storageFolder.prepend(root); } if (!storageFolder.isEmpty() && !QFile::exists(storageFolder) && projectDir.exists(documentid)) { storageFolder = projectDir.absolutePath(); Xml::setXmlProperty(playlists.at(i).toElement(), QStringLiteral("kdenlive:docproperties.storagefolder"), projectDir.absoluteFilePath(documentid)); m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); } break; } } QDomNodeList documentProducers = m_doc.elementsByTagName(QStringLiteral("producer")); QDomElement profile = baseElement.firstChildElement(QStringLiteral("profile")); bool hdProfile = true; if (!profile.isNull()) { if (profile.attribute(QStringLiteral("width")).toInt() < 1000) { hdProfile = false; } } // List clips whose proxy is missing QList missingProxies; // List clips who have a working proxy but no source clip QList missingSources; m_safeImages.clear(); m_safeFonts.clear(); m_missingFonts.clear(); max = documentProducers.count(); QStringList verifiedPaths; QStringList missingPaths; QStringList serviceToCheck; serviceToCheck << QStringLiteral("kdenlivetitle") << QStringLiteral("qimage") << QStringLiteral("pixbuf") << QStringLiteral("timewarp") << QStringLiteral("framebuffer") << QStringLiteral("xml") << QStringLiteral("qtext"); for (int i = 0; i < max; ++i) { QDomElement e = documentProducers.item(i).toElement(); QString service = Xml::getXmlProperty(e, QStringLiteral("mlt_service")); if (!service.startsWith(QLatin1String("avformat")) && !serviceToCheck.contains(service)) { continue; } if (service == QLatin1String("qtext")) { QString text = Xml::getXmlProperty(e, QStringLiteral("text")); if (text == QLatin1String("INVALID")) { // Warning, this is an invalid clip (project saved with missing source) // Check if source clip is now available QString resource = Xml::getXmlProperty(e, QStringLiteral("warp_resource")); if (resource.isEmpty()) { resource = Xml::getXmlProperty(e, QStringLiteral("resource")); } // Make sure to have absolute paths if (QFileInfo(resource).isRelative()) { resource.prepend(root); } if (QFile::exists(resource)) { // Reset to original service Xml::removeXmlProperty(e, QStringLiteral("text")); QString original_service = Xml::getXmlProperty(e, QStringLiteral("kdenlive:orig_service")); if (!original_service.isEmpty()) { Xml::setXmlProperty(e, QStringLiteral("mlt_service"), original_service); } else { // Try to guess service if (Xml::hasXmlProperty(e, QStringLiteral("ttl"))) { Xml::setXmlProperty(e, QStringLiteral("mlt_service"), QStringLiteral("qimage")); } else if (resource.endsWith(QLatin1String(".kdenlivetitle"))) { Xml::setXmlProperty(e, QStringLiteral("mlt_service"), QStringLiteral("kdenlivetitle")); } else if (resource.endsWith(QLatin1String(".kdenlive")) || resource.endsWith(QLatin1String(".mlt"))) { Xml::setXmlProperty(e, QStringLiteral("mlt_service"), QStringLiteral("xml")); } else { Xml::setXmlProperty(e, QStringLiteral("mlt_service"), QStringLiteral("avformat")); } } } continue; } checkMissingImagesAndFonts(QStringList(), QStringList(Xml::getXmlProperty(e, QStringLiteral("family"))), e.attribute(QStringLiteral("id")), e.attribute(QStringLiteral("name"))); continue; } if (service == QLatin1String("kdenlivetitle")) { // TODO: Check is clip template is missing (xmltemplate) or hash changed QString xml = Xml::getXmlProperty(e, QStringLiteral("xmldata")); QStringList images = TitleWidget::extractImageList(xml); QStringList fonts = TitleWidget::extractFontList(xml); checkMissingImagesAndFonts(images, fonts, e.attribute(QStringLiteral("id")), e.attribute(QStringLiteral("name"))); continue; } QString resource = Xml::getXmlProperty(e, QStringLiteral("resource")); if (resource.isEmpty()) { continue; } if (service == QLatin1String("timewarp")) { // slowmotion clip, trim speed info resource = Xml::getXmlProperty(e, QStringLiteral("warp_resource")); } else if (service == QLatin1String("framebuffer")) { // slowmotion clip, trim speed info resource = resource.section(QLatin1Char('?'), 0, 0); } // Make sure to have absolute paths if (QFileInfo(resource).isRelative()) { resource.prepend(root); } if (verifiedPaths.contains(resource)) { // Don't check same url twice (for example track producers) if (missingPaths.contains(resource)) { m_missingClips.append(e); } continue; } QString proxy = Xml::getXmlProperty(e, QStringLiteral("kdenlive:proxy")); if (proxy.length() > 1) { if (QFileInfo(proxy).isRelative()) { proxy.prepend(root); } if (!QFile::exists(proxy)) { // Missing clip found // Check if proxy exists in current storage folder bool fixed = false; if (!storageFolder.isEmpty()) { QDir dir(storageFolder + QStringLiteral("/proxy/")); if (dir.exists(QFileInfo(proxy).fileName())) { QString updatedPath = dir.absoluteFilePath(QFileInfo(proxy).fileName()); fixProxyClip(e.attribute(QStringLiteral("id")), Xml::getXmlProperty(e, QStringLiteral("kdenlive:proxy")), updatedPath, documentProducers); fixed = true; } } if (!fixed) { missingProxies.append(e); } } QString original = Xml::getXmlProperty(e, QStringLiteral("kdenlive:originalurl")); if (QFileInfo(original).isRelative()) { original.prepend(root); } // Check for slideshows bool slideshow = original.contains(QStringLiteral("/.all.")) || original.contains(QLatin1Char('?')) || original.contains(QLatin1Char('%')); if (slideshow && !Xml::getXmlProperty(e, QStringLiteral("ttl")).isEmpty()) { original = QFileInfo(original).absolutePath(); } if (!QFile::exists(original)) { // clip has proxy but original clip is missing missingSources.append(e); missingPaths.append(resource); } verifiedPaths.append(resource); continue; } // Check for slideshows bool slideshow = resource.contains(QStringLiteral("/.all.")) || resource.contains(QLatin1Char('?')) || resource.contains(QLatin1Char('%')); if ((service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) && slideshow) { resource = QFileInfo(resource).absolutePath(); } if (!QFile::exists(resource)) { // Missing clip found, make sure to omit timeline preview if (QFileInfo(resource).absolutePath().endsWith(QString("/%1/preview").arg(documentid))) { // This is a timeline preview missing chunk, ignore } else { m_missingClips.append(e); missingPaths.append(resource); } } // Make sure we don't query same path twice verifiedPaths.append(resource); } // Get list of used Luma files QStringList missingLumas; QStringList filesToCheck; QString filePath; QMap lumaSearchPairs = getLumaPairs(); QDomNodeList trans = m_doc.elementsByTagName(QStringLiteral("transition")); max = trans.count(); for (int i = 0; i < max; ++i) { QDomElement transition = trans.at(i).toElement(); QString service = getProperty(transition, QStringLiteral("mlt_service")); QString luma; if (lumaSearchPairs.contains(service)) { luma = getProperty(transition, lumaSearchPairs.value(service)); } if (!luma.isEmpty() && !filesToCheck.contains(luma)) { filesToCheck.append(luma); } } QMap autoFixLuma; QString lumaPath; + QString lumaMltPath; // Check existence of luma files for (const QString &lumafile : filesToCheck) { filePath = lumafile; if (QFileInfo(filePath).isRelative()) { filePath.prepend(root); } if (!QFile::exists(filePath)) { QString lumaName = filePath.section(QLatin1Char('/'), -1); // check if this was an old format luma, not in correct folder QString fixedLuma = filePath.section(QLatin1Char('/'), 0, -2); lumaName.prepend(hdProfile ? QStringLiteral("/HD/") : QStringLiteral("/PAL/")); fixedLuma.append(lumaName); if (QFile::exists(fixedLuma)) { // Auto replace pgm with png for lumas autoFixLuma.insert(filePath, fixedLuma); continue; } - // Check MLT folder + // Check Kdenlive folder if (lumaPath.isEmpty()) { QDir dir(QCoreApplication::applicationDirPath()); dir.cdUp(); dir.cd(QStringLiteral("share/kdenlive/lumas/")); - lumaPath = dir.absolutePath(); + lumaPath = dir.absolutePath() + QStringLiteral("/"); } + lumaName = filePath.section(QLatin1Char('/'), -2); lumaName.prepend(lumaPath); if (QFile::exists(lumaName)) { autoFixLuma.insert(filePath, lumaName); continue; } + // Check MLT folder + if (lumaMltPath.isEmpty()) { + QDir dir(KdenliveSettings::mltpath()); + dir.cd(QStringLiteral("../lumas/")); + lumaMltPath = dir.absolutePath() + QStringLiteral("/"); + } + lumaName = filePath.section(QLatin1Char('/'), -2); + lumaName.prepend(lumaMltPath); + if (QFile::exists(lumaName)) { + autoFixLuma.insert(filePath, lumaName); + continue; + } if (filePath.endsWith(QLatin1String(".pgm"))) { fixedLuma = filePath.section(QLatin1Char('.'), 0, -2) + QStringLiteral(".png"); } else if (filePath.endsWith(QLatin1String(".png"))) { fixedLuma = filePath.section(QLatin1Char('.'), 0, -2) + QStringLiteral(".pgm"); } if (!fixedLuma.isEmpty() && QFile::exists(fixedLuma)) { // Auto replace pgm with png for lumas autoFixLuma.insert(filePath, fixedLuma); } else { missingLumas.append(lumafile); } } } if (!autoFixLuma.isEmpty()) { for (int i = 0; i < max; ++i) { QDomElement transition = trans.at(i).toElement(); QString service = getProperty(transition, QStringLiteral("mlt_service")); QString luma; if (lumaSearchPairs.contains(service)) { luma = getProperty(transition, lumaSearchPairs.value(service)); } if (!luma.isEmpty() && autoFixLuma.contains(luma)) { updateProperty(transition, lumaSearchPairs.value(service), autoFixLuma.value(luma)); } } } // Check for missing effects QDomNodeList effs = m_doc.elementsByTagName(QStringLiteral("filter")); max = effs.count(); QStringList filters; for (int i = 0; i < max; ++i) { QDomElement transition = effs.at(i).toElement(); QString service = getProperty(transition, QStringLiteral("kdenlive_id")); filters << service; } QStringList processed; for (const QString &id : filters) { if (!processed.contains(id) && !EffectsRepository::get()->exists(id)) { m_missingFilters << id; } processed << id; } if (!m_missingFilters.isEmpty()) { // Delete missing effects for (int i = 0; i < effs.count(); ++i) { QDomElement e = effs.item(i).toElement(); if (m_missingFilters.contains(getProperty(e, QStringLiteral("kdenlive_id")))) { // Remove clip e.parentNode().removeChild(e); --i; } } } if (m_missingClips.isEmpty() && missingLumas.isEmpty() && missingProxies.isEmpty() && missingSources.isEmpty() && m_missingFonts.isEmpty() && m_missingFilters.isEmpty()) { return false; } m_dialog = new QDialog(); m_dialog->setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); m_ui.setupUi(m_dialog); for (const QString &l : missingLumas) { QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Luma file") << l); item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-close"))); item->setData(0, idRole, l); item->setData(0, statusRole, LUMAMISSING); } m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_missingClips.isEmpty() && missingProxies.isEmpty() && missingSources.isEmpty()); max = m_missingClips.count(); m_missingProxyIds.clear(); QStringList processedIds; for (int i = 0; i < max; ++i) { QDomElement e = m_missingClips.at(i).toElement(); QString clipType; ClipType::ProducerType type; int status = CLIPMISSING; const QString service = Xml::getXmlProperty(e, QStringLiteral("mlt_service")); QString resource = service == QLatin1String("timewarp") ? Xml::getXmlProperty(e, QStringLiteral("warp_resource")) : Xml::getXmlProperty(e, QStringLiteral("resource")); bool slideshow = resource.contains(QStringLiteral("/.all.")) || resource.contains(QLatin1Char('?')) || resource.contains(QLatin1Char('%')); if (service == QLatin1String("avformat") || service == QLatin1String("avformat-novalidate") || service == QLatin1String("framebuffer") || service == QLatin1String("timewarp")) { clipType = i18n("Video clip"); type = ClipType::AV; } else if (service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) { if (slideshow) { clipType = i18n("Slideshow clip"); type = ClipType::SlideShow; } else { clipType = i18n("Image clip"); type = ClipType::Image; } } else if (service == QLatin1String("mlt") || service == QLatin1String("xml")) { clipType = i18n("Playlist clip"); type = ClipType::Playlist; } else if (e.tagName() == QLatin1String("missingtitle")) { clipType = i18n("Title Image"); status = TITLE_IMAGE_ELEMENT; type = ClipType::Text; } else { clipType = i18n("Unknown"); type = ClipType::Unknown; } // Newer project format QString clipId = Xml::getXmlProperty(e, QStringLiteral("kdenlive:id")); if (!clipId.isEmpty()) { if (processedIds.contains(clipId)) { continue; } processedIds << clipId; } else { // Older project file format clipId = e.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0); if (processedIds.contains(clipId)) { continue; } processedIds << clipId; } QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType); item->setData(0, statusRole, CLIPMISSING); item->setData(0, clipTypeRole, (int)type); item->setData(0, idRole, e.attribute(QStringLiteral("id"))); item->setToolTip(0, i18n("Missing item")); if (status == TITLE_IMAGE_ELEMENT) { item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-warning"))); item->setToolTip(1, e.attribute(QStringLiteral("name"))); QString imageResource = e.attribute(QStringLiteral("resource")); item->setData(0, typeRole, status); item->setData(0, typeOriginalResource, e.attribute(QStringLiteral("resource"))); if (!m_rootReplacement.first.isEmpty()) { if (imageResource.startsWith(m_rootReplacement.first)) { imageResource.replace(m_rootReplacement.first, m_rootReplacement.second); if (QFile::exists(imageResource)) { item->setData(0, statusRole, CLIPOK); item->setToolTip(0, i18n("Relocated item")); } } } item->setText(1, imageResource); } else { item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-close"))); if (QFileInfo(resource).isRelative()) { resource.prepend(root); } item->setData(0, hashRole, Xml::getXmlProperty(e, QStringLiteral("kdenlive:file_hash"))); item->setData(0, sizeRole, Xml::getXmlProperty(e, QStringLiteral("kdenlive:file_size"))); if (!m_rootReplacement.first.isEmpty()) { if (resource.startsWith(m_rootReplacement.first)) { resource.replace(m_rootReplacement.first, m_rootReplacement.second); if (QFile::exists(resource)) { item->setData(0, statusRole, CLIPOK); item->setToolTip(0, i18n("Relocated item")); } } } item->setText(1, resource); } } for (const QString &font : m_missingFonts) { QString clipType = i18n("Title Font"); QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType); item->setData(0, statusRole, CLIPPLACEHOLDER); item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-warning"))); QString newft = QFontInfo(QFont(font)).family(); item->setText(1, i18n("%1 will be replaced by %2", font, newft)); item->setData(0, typeRole, TITLE_FONT_ELEMENT); } QString infoLabel; if (!m_missingClips.isEmpty()) { infoLabel = i18n("The project file contains missing clips or files."); } if (!m_missingFilters.isEmpty()) { if (!infoLabel.isEmpty()) { infoLabel.append(QStringLiteral("\n")); } infoLabel.append(i18np("Missing effect: %2 will be removed from project.", "Missing effects: %2 will be removed from project.", m_missingFilters.count(), m_missingFilters.join(","))); } if (!missingProxies.isEmpty()) { if (!infoLabel.isEmpty()) { infoLabel.append(QStringLiteral("\n")); } infoLabel.append(i18n("Missing proxies will be recreated after opening.")); } if (!missingSources.isEmpty()) { if (!infoLabel.isEmpty()) { infoLabel.append(QStringLiteral("\n")); } infoLabel.append(i18np("The project file contains a missing clip, you can still work with its proxy.", "The project file contains %1 missing clips, you can still work with their proxies.", missingSources.count())); } if (!infoLabel.isEmpty()) { m_ui.infoLabel->setText(infoLabel); } else { m_ui.infoLabel->setVisible(false); } m_ui.removeSelected->setEnabled(!m_missingClips.isEmpty()); m_ui.recursiveSearch->setEnabled(!m_missingClips.isEmpty() || !missingLumas.isEmpty() || !missingSources.isEmpty()); m_ui.usePlaceholders->setEnabled(!m_missingClips.isEmpty()); // Check missing proxies max = missingProxies.count(); if (max > 0) { QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Proxy clip")); item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-warning"))); item->setText( 1, i18np("%1 missing proxy clip, will be recreated on project opening", "%1 missing proxy clips, will be recreated on project opening", max)); // item->setData(0, hashRole, e.attribute("file_hash")); item->setData(0, statusRole, PROXYMISSING); item->setToolTip(0, i18n("Missing proxy")); } for (int i = 0; i < max; ++i) { QDomElement e = missingProxies.at(i).toElement(); QString realPath = Xml::getXmlProperty(e, QStringLiteral("kdenlive:originalurl")); QString id = e.attribute(QStringLiteral("id")); m_missingProxyIds << id; // Tell Kdenlive to recreate proxy e.setAttribute(QStringLiteral("_replaceproxy"), QStringLiteral("1")); // Replace proxy url with real clip in MLT producers QDomElement mltProd; int prodsCount = documentProducers.count(); for (int j = 0; j < prodsCount; ++j) { mltProd = documentProducers.at(j).toElement(); QString prodId = mltProd.attribute(QStringLiteral("id")); QString parentId = prodId; bool slowmotion = false; if (parentId.startsWith(QLatin1String("slowmotion"))) { slowmotion = true; parentId = parentId.section(QLatin1Char(':'), 1, 1); } if (parentId.contains(QLatin1Char('_'))) { parentId = parentId.section(QLatin1Char('_'), 0, 0); } if (parentId == id) { // Hit, we must replace url QString suffix; QString resource = Xml::getXmlProperty(mltProd, QStringLiteral("resource")); if (slowmotion) { suffix = QLatin1Char('?') + resource.section(QLatin1Char('?'), -1); } Xml::setXmlProperty(mltProd, QStringLiteral("resource"), realPath + suffix); if (prodId == id) { // Only set proxy property on master producer Xml::setXmlProperty(mltProd, QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); } } } } if (max > 0) { // original doc was modified m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); } // Check clips with available proxies but missing original source clips max = missingSources.count(); if (max > 0) { QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Source clip")); item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-warning"))); item->setText(1, i18n("%1 missing source clips, you can only use the proxies", max)); // item->setData(0, hashRole, e.attribute("file_hash")); item->setData(0, statusRole, SOURCEMISSING); item->setToolTip(0, i18n("Missing source clip")); for (int i = 0; i < max; ++i) { QDomElement e = missingSources.at(i).toElement(); QString realPath = Xml::getXmlProperty(e, QStringLiteral("kdenlive:originalurl")); QString id = e.attribute(QStringLiteral("id")); // Tell Kdenlive the source is missing e.setAttribute(QStringLiteral("_missingsource"), QStringLiteral("1")); QTreeWidgetItem *subitem = new QTreeWidgetItem(item, QStringList() << i18n("Source clip")); // qCDebug(KDENLIVE_LOG)<<"// Adding missing source clip: "<setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-close"))); subitem->setText(1, realPath); subitem->setData(0, hashRole, Xml::getXmlProperty(e, QStringLiteral("kdenlive:file_hash"))); subitem->setData(0, sizeRole, Xml::getXmlProperty(e, QStringLiteral("kdenlive:file_size"))); subitem->setData(0, statusRole, CLIPMISSING); // int t = e.attribute("type").toInt(); subitem->setData(0, typeRole, Xml::getXmlProperty(e, QStringLiteral("mlt_service"))); subitem->setData(0, idRole, id); } } if (max > 0) { // original doc was modified m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); } m_ui.treeWidget->resizeColumnToContents(0); connect(m_ui.recursiveSearch, &QAbstractButton::pressed, this, &DocumentChecker::slotSearchClips); connect(m_ui.usePlaceholders, &QAbstractButton::pressed, this, &DocumentChecker::slotPlaceholders); connect(m_ui.removeSelected, &QAbstractButton::pressed, this, &DocumentChecker::slotDeleteSelected); connect(m_ui.treeWidget, &QTreeWidget::itemDoubleClicked, this, &DocumentChecker::slotEditItem); connect(m_ui.treeWidget, &QTreeWidget::itemSelectionChanged, this, &DocumentChecker::slotCheckButtons); // adjustSize(); if (m_ui.treeWidget->topLevelItem(0)) { m_ui.treeWidget->setCurrentItem(m_ui.treeWidget->topLevelItem(0)); } checkStatus(); int acceptMissing = m_dialog->exec(); if (acceptMissing == QDialog::Accepted) { acceptDialog(); } return (acceptMissing != QDialog::Accepted); } DocumentChecker::~DocumentChecker() { delete m_dialog; } QString DocumentChecker::getProperty(const QDomElement &effect, const QString &name) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")) == name) { return e.firstChild().nodeValue(); } } return QString(); } void DocumentChecker::updateProperty(const QDomElement &effect, const QString &name, const QString &value) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")) == name) { e.firstChild().setNodeValue(value); break; } } } void DocumentChecker::setProperty(QDomElement &effect, const QString &name, const QString &value) { QDomNodeList params = effect.elementsByTagName(QStringLiteral("property")); bool found = false; for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("name")) == name) { e.firstChild().setNodeValue(value); found = true; break; } } if (!found) { // create property QDomDocument doc = effect.ownerDocument(); QDomElement e = doc.createElement(QStringLiteral("property")); e.setAttribute(QStringLiteral("name"), name); QDomText val = doc.createTextNode(value); e.appendChild(val); effect.appendChild(e); } } void DocumentChecker::slotSearchClips() { // QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); QString clipFolder = m_url.adjusted(QUrl::RemoveFilename).toLocalFile(); QString newpath = QFileDialog::getExistingDirectory(qApp->activeWindow(), i18n("Clips folder"), clipFolder); if (newpath.isEmpty()) { return; } int ix = 0; bool fixed = false; m_ui.recursiveSearch->setChecked(true); // TODO: make non modal QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix); QDir searchDir(newpath); while (child != nullptr) { if (child->data(0, statusRole).toInt() == SOURCEMISSING) { for (int j = 0; j < child->childCount(); ++j) { QTreeWidgetItem *subchild = child->child(j); QString clipPath = searchFileRecursively(searchDir, subchild->data(0, sizeRole).toString(), subchild->data(0, hashRole).toString(), subchild->text(1)); if (!clipPath.isEmpty()) { fixed = true; subchild->setText(1, clipPath); subchild->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-ok"))); subchild->setData(0, statusRole, CLIPOK); } } } else if (child->data(0, statusRole).toInt() == CLIPMISSING) { bool perfectMatch = true; ClipType::ProducerType type = (ClipType::ProducerType)child->data(0, clipTypeRole).toInt(); QString clipPath; if (type != ClipType::SlideShow) { // Slideshows cannot be found with hash / size clipPath = searchFileRecursively(searchDir, child->data(0, sizeRole).toString(), child->data(0, hashRole).toString(), child->text(1)); } if (clipPath.isEmpty()) { clipPath = searchPathRecursively(searchDir, QUrl::fromLocalFile(child->text(1)).fileName(), type); perfectMatch = false; } if (!clipPath.isEmpty()) { fixed = true; child->setText(1, clipPath); child->setIcon(0, perfectMatch ? QIcon::fromTheme(QStringLiteral("dialog-ok")) : QIcon::fromTheme(QStringLiteral("dialog-warning"))); child->setData(0, statusRole, CLIPOK); } } else if (child->data(0, statusRole).toInt() == LUMAMISSING) { QString fileName = searchLuma(searchDir, child->data(0, idRole).toString()); if (!fileName.isEmpty()) { fixed = true; child->setText(1, fileName); child->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-ok"))); child->setData(0, statusRole, LUMAOK); } } else if (child->data(0, typeRole).toInt() == TITLE_IMAGE_ELEMENT && child->data(0, statusRole).toInt() == CLIPPLACEHOLDER) { // Search missing title images QString missingFileName = QUrl::fromLocalFile(child->text(1)).fileName(); QString newPath = searchPathRecursively(searchDir, missingFileName); if (!newPath.isEmpty()) { // File found fixed = true; child->setText(1, newPath); child->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-ok"))); child->setData(0, statusRole, CLIPOK); } } ix++; child = m_ui.treeWidget->topLevelItem(ix); } m_ui.recursiveSearch->setChecked(false); m_ui.recursiveSearch->setEnabled(true); if (fixed) { // original doc was modified m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); } checkStatus(); } QString DocumentChecker::searchLuma(const QDir &dir, const QString &file) const { QDir searchPath(KdenliveSettings::mltpath()); QString fname = QUrl::fromLocalFile(file).fileName(); if (file.contains(QStringLiteral("PAL"))) { searchPath.cd(QStringLiteral("../lumas/PAL")); } else { searchPath.cd(QStringLiteral("../lumas/NTSC")); } QFileInfo result(searchPath, fname); if (result.exists()) { return result.filePath(); } // try to find luma in application path searchPath.setPath(QCoreApplication::applicationDirPath()); #ifdef Q_OS_WIN searchPath.cd(QStringLiteral("data/lumas")); #else searchPath.cd(QStringLiteral("../share/apps/kdenlive/lumas")); #endif result.setFile(searchPath, fname); if (result.exists()) { return result.filePath(); } // Try in Kdenlive's standard KDE path QString res = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("lumas/") + fname); if (!res.isEmpty()) { return res; } // Try in user's chosen folder return searchPathRecursively(dir, fname); } QString DocumentChecker::searchPathRecursively(const QDir &dir, const QString &fileName, ClipType::ProducerType type) const { QString foundFileName; QStringList filters; if (type == ClipType::SlideShow) { if (fileName.contains(QLatin1Char('%'))) { filters << fileName.section(QLatin1Char('%'), 0, -2) + QLatin1Char('*'); } else { return QString(); } } else { filters << fileName; } QDir searchDir(dir); searchDir.setNameFilters(filters); QStringList filesAndDirs = searchDir.entryList(QDir::Files | QDir::Readable); if (!filesAndDirs.isEmpty()) { // File Found if (type == ClipType::SlideShow) { return searchDir.absoluteFilePath(fileName); } return searchDir.absoluteFilePath(filesAndDirs.at(0)); } searchDir.setNameFilters(QStringList()); filesAndDirs = searchDir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot); for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { foundFileName = searchPathRecursively(searchDir.absoluteFilePath(filesAndDirs.at(i)), fileName, type); if (!foundFileName.isEmpty()) { break; } } return foundFileName; } QString DocumentChecker::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash, const QString &fileName) const { if (matchSize.isEmpty() && matchHash.isEmpty()) { return searchPathRecursively(dir, QUrl::fromLocalFile(fileName).fileName()); } QString foundFileName; QByteArray fileData; QByteArray fileHash; QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable); for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { QFile file(dir.absoluteFilePath(filesAndDirs.at(i))); if (QString::number(file.size()) == matchSize) { if (file.open(QIODevice::ReadOnly)) { /* * 1 MB = 1 second per 450 files (or faster) * 10 MB = 9 seconds per 450 files (or faster) */ if (file.size() > 1000000 * 2) { fileData = file.read(1000000); if (file.seek(file.size() - 1000000)) { fileData.append(file.readAll()); } } else { fileData = file.readAll(); } file.close(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); if (QString::fromLatin1(fileHash.toHex()) == matchHash) { return file.fileName(); } } } ////qCDebug(KDENLIVE_LOG) << filesAndDirs.at(i) << file.size() << fileHash.toHex(); } filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot); for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash, fileName); if (!foundFileName.isEmpty()) { break; } } return foundFileName; } void DocumentChecker::slotEditItem(QTreeWidgetItem *item, int) { int t = item->data(0, typeRole).toInt(); if (t == TITLE_FONT_ELEMENT) { return; } //|| t == TITLE_IMAGE_ELEMENT) { QUrl url = KUrlRequesterDialog::getUrl(QUrl::fromLocalFile(item->text(1)), m_dialog, i18n("Enter new location for file")); if (!url.isValid()) { return; } item->setText(1, url.toLocalFile()); ClipType::ProducerType type = (ClipType::ProducerType)item->data(0, clipTypeRole).toInt(); bool fixed = false; if (type == ClipType::SlideShow && QFile::exists(url.adjusted(QUrl::RemoveFilename).toLocalFile())) { fixed = true; } if (fixed || QFile::exists(url.toLocalFile())) { item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-ok"))); int id = item->data(0, statusRole).toInt(); if (id < 10) { item->setData(0, statusRole, CLIPOK); } else { item->setData(0, statusRole, LUMAOK); } checkStatus(); } else { item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-close"))); int id = item->data(0, statusRole).toInt(); if (id < 10) { item->setData(0, statusRole, CLIPMISSING); } else { item->setData(0, statusRole, LUMAMISSING); } checkStatus(); } } void DocumentChecker::acceptDialog() { QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer")); int ix = 0; // prepare transitions QDomNodeList trans = m_doc.elementsByTagName(QStringLiteral("transition")); // prepare filters QDomNodeList filters = m_doc.elementsByTagName(QStringLiteral("filter")); // Mark document as modified m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix); while (child != nullptr) { if (child->data(0, statusRole).toInt() == SOURCEMISSING) { for (int j = 0; j < child->childCount(); ++j) { fixSourceClipItem(child->child(j), producers); } } else { fixClipItem(child, producers, trans); } ix++; child = m_ui.treeWidget->topLevelItem(ix); } // QDialog::accept(); } void DocumentChecker::fixProxyClip(const QString &id, const QString &oldUrl, const QString &newUrl, const QDomNodeList &producers) { QDomElement e, property; QDomNodeList properties; for (int i = 0; i < producers.count(); ++i) { e = producers.item(i).toElement(); QString parentId = Xml::getXmlProperty(e, QStringLiteral("kdenlive:id")); if (parentId.isEmpty()) { // This is probably an old project file QString sourceId = e.attribute(QStringLiteral("id")); parentId = sourceId.section(QLatin1Char('_'), 0, 0); if (parentId.startsWith(QLatin1String("slowmotion"))) { parentId = parentId.section(QLatin1Char(':'), 1, 1); } } if (parentId == id) { // Fix clip QString resource = Xml::getXmlProperty(e, QStringLiteral("resource")); // TODO: Slowmmotion clips if (resource.contains(QRegExp(QStringLiteral("\\?[0-9]+\\.[0-9]+(&strobe=[0-9]+)?$")))) { // fixedResource.append(QLatin1Char('?') + resource.section(QLatin1Char('?'), -1)); } if (resource == oldUrl) { Xml::setXmlProperty(e, QStringLiteral("resource"), newUrl); } if (!Xml::getXmlProperty(e, QStringLiteral("kdenlive:proxy")).isEmpty()) { // Only set originalurl on master producer Xml::setXmlProperty(e, QStringLiteral("kdenlive:proxy"), newUrl); } } } } void DocumentChecker::fixSourceClipItem(QTreeWidgetItem *child, const QDomNodeList &producers) { QDomElement e, property; QDomNodeList properties; // int t = child->data(0, typeRole).toInt(); if (child->data(0, statusRole).toInt() == CLIPOK) { QString id = child->data(0, idRole).toString(); for (int i = 0; i < producers.count(); ++i) { e = producers.item(i).toElement(); QString parentId = Xml::getXmlProperty(e, QStringLiteral("kdenlive:id")); if (parentId.isEmpty()) { // This is probably an old project file QString sourceId = e.attribute(QStringLiteral("id")); parentId = sourceId.section(QLatin1Char('_'), 0, 0); if (parentId.startsWith(QLatin1String("slowmotion"))) { parentId = parentId.section(QLatin1Char(':'), 1, 1); } } if (parentId == id) { // Fix clip QString resource = Xml::getXmlProperty(e, QStringLiteral("resource")); QString fixedResource = child->text(1); if (resource.contains(QRegExp(QStringLiteral("\\?[0-9]+\\.[0-9]+(&strobe=[0-9]+)?$")))) { fixedResource.append(QLatin1Char('?') + resource.section(QLatin1Char('?'), -1)); } if (!Xml::getXmlProperty(e, QStringLiteral("kdenlive:originalurl")).isEmpty()) { // Only set originalurl on master producer Xml::setXmlProperty(e, QStringLiteral("kdenlive:originalurl"), fixedResource); } if (m_missingProxyIds.contains(parentId)) { // Proxy is also missing, replace resource Xml::setXmlProperty(e, QStringLiteral("resource"), fixedResource); } } } } } void DocumentChecker::fixClipItem(QTreeWidgetItem *child, const QDomNodeList &producers, const QDomNodeList &trans) { QDomElement e, property; QDomNodeList properties; int t = child->data(0, typeRole).toInt(); QString id = child->data(0, idRole).toString(); if (child->data(0, statusRole).toInt() == CLIPOK) { QString fixedResource = child->text(1); if (t == TITLE_IMAGE_ELEMENT) { // edit images embedded in titles for (int i = 0; i < producers.count(); ++i) { e = producers.item(i).toElement(); QString parentId = Xml::getXmlProperty(e, QStringLiteral("kdenlive:id")); if (parentId.isEmpty()) { // This is probably an old project file QString sourceId = e.attribute(QStringLiteral("id")); parentId = sourceId.section(QLatin1Char('_'), 0, 0); if (parentId.startsWith(QLatin1String("slowmotion"))) { parentId = parentId.section(QLatin1Char(':'), 1, 1); } } if (parentId == id) { // Fix clip properties = e.childNodes(); for (int j = 0; j < properties.count(); ++j) { property = properties.item(j).toElement(); if (property.attribute(QStringLiteral("name")) == QLatin1String("xmldata")) { QString xml = property.firstChild().nodeValue(); xml.replace(child->data(0, typeOriginalResource).toString(), fixedResource); property.firstChild().setNodeValue(xml); break; } } } } } else { // edit clip url /*for (int i = 0; i < infoproducers.count(); ++i) { e = infoproducers.item(i).toElement(); if (e.attribute("id") == id) { // Fix clip e.setAttribute("resource", child->text(1)); e.setAttribute("name", QUrl(child->text(1)).fileName()); e.removeAttribute("_missingsource"); break; } }*/ for (int i = 0; i < producers.count(); ++i) { e = producers.item(i).toElement(); if (e.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0) == id || e.attribute(QStringLiteral("id")).section(QLatin1Char(':'), 1, 1) == id || e.attribute(QStringLiteral("id")) == id) { // Fix clip QString resource = getProperty(e, QStringLiteral("resource")); QString service = getProperty(e, QStringLiteral("mlt_service")); QString updatedResource = fixedResource; if (resource.contains(QRegExp(QStringLiteral("\\?[0-9]+\\.[0-9]+(&strobe=[0-9]+)?$")))) { updatedResource.append(QLatin1Char('?') + resource.section(QLatin1Char('?'), -1)); } if (service == QLatin1String("timewarp")) { updateProperty(e, QStringLiteral("warp_resource"), updatedResource); updatedResource.prepend(getProperty(e, QStringLiteral("warp_speed")) + QLatin1Char(':')); } updateProperty(e, QStringLiteral("resource"), updatedResource); } } } } else if (child->data(0, statusRole).toInt() == CLIPPLACEHOLDER && t != TITLE_FONT_ELEMENT && t != TITLE_IMAGE_ELEMENT) { // QString id = child->data(0, idRole).toString(); for (int i = 0; i < producers.count(); ++i) { e = producers.item(i).toElement(); if (e.attribute("id") == id) { // Fix clip setProperty(e, QStringLiteral("_placeholder"), QStringLiteral("1")); setProperty(e, QStringLiteral("kdenlive:orig_service"), getProperty(e, QStringLiteral("mlt_service"))); break; } } } else if (child->data(0, statusRole).toInt() == LUMAOK) { QMap lumaSearchPairs = getLumaPairs(); for (int i = 0; i < trans.count(); ++i) { QString service = getProperty(trans.at(i).toElement(), QStringLiteral("mlt_service")); QString luma; if (lumaSearchPairs.contains(service)) { luma = getProperty(trans.at(i).toElement(), lumaSearchPairs.value(service)); } if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) { updateProperty(trans.at(i).toElement(), lumaSearchPairs.value(service), child->text(1)); // qCDebug(KDENLIVE_LOG) << "replace with; " << child->text(1); } } } else if (child->data(0, statusRole).toInt() == LUMAMISSING) { QMap lumaSearchPairs = getLumaPairs(); for (int i = 0; i < trans.count(); ++i) { QString service = getProperty(trans.at(i).toElement(), QStringLiteral("mlt_service")); QString luma; if (lumaSearchPairs.contains(service)) { luma = getProperty(trans.at(i).toElement(), lumaSearchPairs.value(service)); } if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) { updateProperty(trans.at(i).toElement(), lumaSearchPairs.value(service), QString()); } } } } void DocumentChecker::slotPlaceholders() { int ix = 0; QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix); while (child != nullptr) { if (child->data(0, statusRole).toInt() == CLIPMISSING) { child->setData(0, statusRole, CLIPPLACEHOLDER); child->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-ok"))); } else if (child->data(0, statusRole).toInt() == LUMAMISSING) { child->setData(0, statusRole, LUMAPLACEHOLDER); child->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-ok"))); } ix++; child = m_ui.treeWidget->topLevelItem(ix); } checkStatus(); } void DocumentChecker::checkStatus() { bool status = true; int ix = 0; QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix); while (child != nullptr) { int childStatus = child->data(0, statusRole).toInt(); if (childStatus == CLIPMISSING) { status = false; break; } ix++; child = m_ui.treeWidget->topLevelItem(ix); } m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status); } void DocumentChecker::slotDeleteSelected() { if (KMessageBox::warningContinueCancel(m_dialog, i18np("This will remove the selected clip from this project", "This will remove the selected clips from this project", m_ui.treeWidget->selectedItems().count()), i18n("Remove clips")) == KMessageBox::Cancel) { return; } QStringList deletedIds; QStringList deletedLumas; QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist")); for (QTreeWidgetItem *child : m_ui.treeWidget->selectedItems()) { int id = child->data(0, statusRole).toInt(); if (id == CLIPMISSING) { deletedIds.append(child->data(0, idRole).toString()); delete child; } else if (id == LUMAMISSING) { deletedLumas.append(child->data(0, idRole).toString()); delete child; } } if (!deletedLumas.isEmpty()) { QDomElement e; QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition")); QMap lumaSearchPairs = getLumaPairs(); for (const QString &lumaPath : deletedLumas) { for (int i = 0; i < transitions.count(); ++i) { e = transitions.item(i).toElement(); QString service = Xml::getXmlProperty(e, QStringLiteral("mlt_service")); QString resource; if (lumaSearchPairs.contains(service)) { resource = getProperty(e, lumaSearchPairs.value(service)); } if (!resource.isEmpty() && resource == lumaPath) { Xml::removeXmlProperty(e, lumaSearchPairs.value(service)); } } } } if (!deletedIds.isEmpty()) { QDomElement e; QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer")); QDomNode mlt = m_doc.elementsByTagName(QStringLiteral("mlt")).at(0); QDomNode kdenlivedoc = m_doc.elementsByTagName(QStringLiteral("kdenlivedoc")).at(0); for (int i = 0; i < producers.count(); ++i) { e = producers.item(i).toElement(); if (deletedIds.contains(e.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0)) || deletedIds.contains(e.attribute(QStringLiteral("id")).section(QLatin1Char(':'), 1, 1).section(QLatin1Char('_'), 0, 0))) { // Remove clip mlt.removeChild(e); --i; } } for (int i = 0; i < playlists.count(); ++i) { QDomNodeList entries = playlists.at(i).toElement().elementsByTagName(QStringLiteral("entry")); for (int j = 0; j < entries.count(); ++j) { e = entries.item(j).toElement(); if (deletedIds.contains(e.attribute(QStringLiteral("producer")).section(QLatin1Char('_'), 0, 0)) || deletedIds.contains(e.attribute(QStringLiteral("producer")).section(QLatin1Char(':'), 1, 1).section(QLatin1Char('_'), 0, 0))) { // Replace clip with blank while (e.childNodes().count() > 0) { e.removeChild(e.firstChild()); } e.setTagName(QStringLiteral("blank")); e.removeAttribute(QStringLiteral("producer")); int length = e.attribute(QStringLiteral("out")).toInt() - e.attribute(QStringLiteral("in")).toInt(); e.setAttribute(QStringLiteral("length"), length); j--; } } } m_doc.documentElement().setAttribute(QStringLiteral("modified"), 1); checkStatus(); } } void DocumentChecker::checkMissingImagesAndFonts(const QStringList &images, const QStringList &fonts, const QString &id, const QString &baseClip) { QDomDocument doc; for (const QString &img : images) { if (m_safeImages.contains(img)) { continue; } if (!QFile::exists(img)) { QDomElement e = doc.createElement(QStringLiteral("missingtitle")); e.setAttribute(QStringLiteral("type"), TITLE_IMAGE_ELEMENT); e.setAttribute(QStringLiteral("resource"), img); e.setAttribute(QStringLiteral("id"), id); e.setAttribute(QStringLiteral("name"), baseClip); m_missingClips.append(e); } else { m_safeImages.append(img); } } for (const QString &fontelement : fonts) { if (m_safeFonts.contains(fontelement)) { continue; } QFont f(fontelement); ////qCDebug(KDENLIVE_LOG) << "/ / / CHK FONTS: " << fontelement << " = " << QFontInfo(f).family(); if (fontelement != QFontInfo(f).family()) { m_missingFonts << fontelement; } else { m_safeFonts.append(fontelement); } } } void DocumentChecker::slotCheckButtons() { if (m_ui.treeWidget->currentItem()) { QTreeWidgetItem *item = m_ui.treeWidget->currentItem(); int t = item->data(0, typeRole).toInt(); int s = item->data(0, statusRole).toInt(); if (t == TITLE_FONT_ELEMENT || t == TITLE_IMAGE_ELEMENT || s == PROXYMISSING) { m_ui.removeSelected->setEnabled(false); } else { m_ui.removeSelected->setEnabled(true); } } } diff --git a/src/mltconnection.cpp b/src/mltconnection.cpp index b4dd6b2ab..535b2786d 100644 --- a/src/mltconnection.cpp +++ b/src/mltconnection.cpp @@ -1,206 +1,206 @@ /* Copyright (C) 2007 Jean-Baptiste Mardelle Copyright (C) 2014 Till Theato This file is part of kdenlive. See www.kdenlive.org. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. */ #include "mltconnection.h" #include "core.h" #include "kdenlivesettings.h" #include "mainwindow.h" #include "mlt_config.h" #include #include #include #include "kdenlive_debug.h" #include #include #include #include std::unique_ptr MltConnection::m_self; MltConnection::MltConnection(const QString &mltPath) { // Disable VDPAU that crashes in multithread environment. // TODO: make configurable setenv("MLT_NO_VDPAU", "1", 1); m_repository = std::unique_ptr(Mlt::Factory::init()); locateMeltAndProfilesPath(mltPath); // Retrieve the list of available producers. QScopedPointer producers(m_repository->producers()); QStringList producersList; int nb_producers = producers->count(); producersList.reserve(nb_producers); for (int i = 0; i < nb_producers; ++i) { producersList << producers->get_name(i); } KdenliveSettings::setProducerslist(producersList); refreshLumas(); } void MltConnection::construct(const QString &mltPath) { if (MltConnection::m_self) { qDebug() << "DEBUG: Warning : trying to open a second mlt connection"; return; } MltConnection::m_self.reset(new MltConnection(mltPath)); } std::unique_ptr &MltConnection::self() { return MltConnection::m_self; } void MltConnection::locateMeltAndProfilesPath(const QString &mltPath) { QString profilePath = mltPath; // environment variables should override other settings if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && qEnvironmentVariableIsSet("MLT_PROFILES_PATH")) { profilePath = qgetenv("MLT_PROFILES_PATH"); } if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && qEnvironmentVariableIsSet("MLT_DATA")) { profilePath = qgetenv("MLT_DATA") + QStringLiteral("/profiles"); } if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && qEnvironmentVariableIsSet("MLT_PREFIX")) { profilePath = qgetenv("MLT_PREFIX") + QStringLiteral("/share/mlt/profiles"); } #ifndef Q_OS_WIN // stored setting should not be considered on windows as MLT is distributed with each new Kdenlive version if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && !KdenliveSettings::mltpath().isEmpty()) profilePath = KdenliveSettings::mltpath(); #endif // try to automatically guess MLT path if installed with the same prefix as kdenlive with default data path if (profilePath.isEmpty() || !QFile::exists(profilePath)) { profilePath = QDir::cleanPath(qApp->applicationDirPath() + QStringLiteral("/../share/mlt/profiles")); } // fallback to build-time definition if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && !QStringLiteral(MLT_DATADIR).isEmpty()) { profilePath = QStringLiteral(MLT_DATADIR) + QStringLiteral("/profiles"); } KdenliveSettings::setMltpath(profilePath); #ifdef Q_OS_WIN QString exeSuffix = ".exe"; #else QString exeSuffix = ""; #endif QString meltPath; if (qEnvironmentVariableIsSet("MLT_PREFIX")) { meltPath = qgetenv("MLT_PREFIX") + QStringLiteral("/bin/melt") + exeSuffix; } else { meltPath = KdenliveSettings::rendererpath(); } if (!QFile::exists(meltPath)) { meltPath = QDir::cleanPath(profilePath + QStringLiteral("/../../../bin/melt")) + exeSuffix; if (!QFile::exists(meltPath)) { meltPath = QStandardPaths::findExecutable("melt"); if (meltPath.isEmpty()) { meltPath = QStandardPaths::findExecutable("mlt-melt"); } } } KdenliveSettings::setRendererpath(meltPath); if (meltPath.isEmpty() && !qEnvironmentVariableIsSet("MLT_TESTS")) { // Cannot find the MLT melt renderer, ask for location QScopedPointer getUrl( new KUrlRequesterDialog(QUrl(), i18n("Cannot find the melt program required for rendering (part of MLT)"), pCore->window())); if (getUrl->exec() == QDialog::Rejected) { ::exit(0); } else { meltPath = getUrl->selectedUrl().toLocalFile(); if (meltPath.isEmpty()) { ::exit(0); } else { KdenliveSettings::setRendererpath(meltPath); } } } if (profilePath.isEmpty()) { profilePath = QDir::cleanPath(meltPath + QStringLiteral("/../../share/mlt/profiles")); KdenliveSettings::setMltpath(profilePath); } QStringList profilesFilter; profilesFilter << QStringLiteral("*"); QStringList profilesList = QDir(profilePath).entryList(profilesFilter, QDir::Files); if (profilesList.isEmpty()) { // Cannot find MLT path, try finding melt if (!meltPath.isEmpty()) { if (meltPath.contains(QLatin1Char('/'))) { profilePath = meltPath.section(QLatin1Char('/'), 0, -2) + QStringLiteral("/share/mlt/profiles"); } else { profilePath = qApp->applicationDirPath() + QStringLiteral("/share/mlt/profiles"); } profilePath = QStringLiteral("/usr/local/share/mlt/profiles"); KdenliveSettings::setMltpath(profilePath); profilesList = QDir(profilePath).entryList(profilesFilter, QDir::Files); } if (profilesList.isEmpty()) { // Cannot find the MLT profiles, ask for location QScopedPointer getUrl( new KUrlRequesterDialog(QUrl::fromLocalFile(profilePath), i18n("Cannot find your MLT profiles, please give the path"), pCore->window())); getUrl->urlRequester()->setMode(KFile::Directory); if (getUrl->exec() == QDialog::Rejected) { ::exit(0); } else { profilePath = getUrl->selectedUrl().toLocalFile(); if (profilePath.isEmpty()) { ::exit(0); } else { KdenliveSettings::setMltpath(profilePath); profilesList = QDir(profilePath).entryList(profilesFilter, QDir::Files); } } } } qCDebug(KDENLIVE_LOG) << "MLT profiles path: " << KdenliveSettings::mltpath(); // Parse again MLT profiles to build a list of available video formats if (profilesList.isEmpty()) { locateMeltAndProfilesPath(); } } std::unique_ptr &MltConnection::getMltRepository() { return m_repository; } void MltConnection::refreshLumas() { // Check for Kdenlive installed luma files, add empty string at start for no luma - QStringList imagefiles; QStringList fileFilters; MainWindow::m_lumaFiles.clear(); fileFilters << QStringLiteral("*.png") << QStringLiteral("*.pgm"); QStringList customLumas = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("lumas"), QStandardPaths::LocateDirectory); customLumas.append(QString(mlt_environment("MLT_DATA")) + QStringLiteral("/lumas")); for (const QString &folder : customLumas) { QDir topDir(folder); QStringList folders = topDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); QString format; for (const QString &f : folders) { + QStringList imagefiles; QDir dir(topDir.absoluteFilePath(f)); - if (f == QLatin1String("16_9")) { - format = QStringLiteral("HD"); + if (f == QLatin1String("HD")) { + format = QStringLiteral("16_9"); } else { format = f; } QStringList filesnames = dir.entryList(fileFilters, QDir::Files); if (MainWindow::m_lumaFiles.contains(format)) { imagefiles = MainWindow::m_lumaFiles.value(format); } for (const QString &fname : filesnames) { imagefiles.append(dir.absoluteFilePath(fname)); } MainWindow::m_lumaFiles.insert(format, imagefiles); } } }