Ensure we check file hash on every project opening to ensure clips have not...

Ensure we check file hash on every project opening to ensure clips have not changed and an incorrect hash is not stored.
parent 1d5899fb
Pipeline #35536 passed with stage
in 11 minutes and 45 seconds
......@@ -996,24 +996,9 @@ const QString ProjectClip::getFileHash()
fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
break;
default:
QFile file(clipUrl());
if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file
/*
* 1 MB = 1 second per 450 files (or faster)
* 10 MB = 9 seconds per 450 files (or faster)
*/
if (file.size() > 2000000) {
fileData = file.read(1000000);
if (file.seek(file.size() - 1000000)) {
fileData.append(file.readAll());
}
} else {
fileData = file.readAll();
}
file.close();
ClipController::setProducerProperty(QStringLiteral("kdenlive:file_size"), QString::number(file.size()));
fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
}
QPair<QByteArray, qint64> hashData = calculateHash(clipUrl());
fileHash = hashData.first;
ClipController::setProducerProperty(QStringLiteral("kdenlive:file_size"), QString::number(hashData.second));
break;
}
if (fileHash.isEmpty()) {
......@@ -1025,6 +1010,33 @@ const QString ProjectClip::getFileHash()
return result;
}
const QPair<QByteArray, qint64> ProjectClip::calculateHash(const QString path)
{
QFile file(path);
QByteArray fileHash;
qint64 fSize = 0;
if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file
/*
* 1 MB = 1 second per 450 files (or faster)
* 10 MB = 9 seconds per 450 files (or faster)
*/
QByteArray fileData;
fSize = file.size();
if (fSize > 2000000) {
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);
}
return {fileHash, fSize};
}
double ProjectClip::getOriginalFps() const
{
return originalFps();
......
......@@ -165,6 +165,8 @@ public:
/** @brief The clip hash created from the clip's resource. */
const QString hash();
/** @brief Callculate a file hash from a path. */
static const QPair<QByteArray, qint64> calculateHash(const QString path);
/** @brief Returns true if we are using a proxy for this clip. */
bool hasProxy() const;
......
......@@ -23,6 +23,7 @@
#include "kdenlivesettings.h"
#include "kthumb.h"
#include "titler/titlewidget.h"
#include "bin/projectclip.h"
#include <KMessageBox>
#include <KRecentDirs>
......@@ -137,6 +138,7 @@ bool DocumentChecker::hasErrorInClips()
m_safeImages.clear();
m_safeFonts.clear();
m_missingFonts.clear();
m_changedClips.clear();
max = documentProducers.count();
QStringList verifiedPaths;
QStringList missingPaths;
......@@ -281,6 +283,17 @@ bool DocumentChecker::hasErrorInClips()
m_missingClips.append(e);
missingPaths.append(resource);
}
} else if (service.startsWith(QLatin1String("avformat"))) {
// Check if file changed
QByteArray hash = Xml::getXmlProperty(e, "kdenlive:file_hash").toLatin1();
if (!hash.isEmpty()) {
QByteArray fileData = ProjectClip::calculateHash(resource).first;
if (hash != QCryptographicHash::hash(fileData, QCryptographicHash::Md5)) {
// Clip was changed, notify and trigger clip reload
Xml::removeXmlProperty(e, "kdenlive:file_hash");
m_changedClips.append(resource);
}
}
}
// Make sure we don't query same path twice
verifiedPaths.append(resource);
......@@ -410,7 +423,7 @@ bool DocumentChecker::hasErrorInClips()
}
}
if (m_missingClips.isEmpty() && missingLumas.isEmpty() && missingProxies.isEmpty() && missingSources.isEmpty() && m_missingFonts.isEmpty() &&
m_missingFilters.isEmpty()) {
m_missingFilters.isEmpty() && m_changedClips.isEmpty()) {
return false;
}
......@@ -522,11 +535,20 @@ bool DocumentChecker::hasErrorInClips()
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")));
item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-information")));
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);
}
for (const QString &url : qAsConst(m_changedClips)) {
QString clipType = i18n("Modified Clips");
QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType);
item->setData(0, statusRole, CLIPPLACEHOLDER);
item->setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-information")));
item->setText(1, i18n("Clip %1 will be reloaded", url));
item->setData(0, typeRole, TITLE_FONT_ELEMENT);
}
QString infoLabel;
if (!m_missingClips.isEmpty()) {
......@@ -564,6 +586,13 @@ bool DocumentChecker::hasErrorInClips()
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 (!m_changedClips.isEmpty()) {
if (!infoLabel.isEmpty()) {
infoLabel.append(QStringLiteral("\n"));
}
infoLabel.append(i18np("The project file contains one modified clip, it will be reloaded.",
"The project file contains %1 modified clips, they will be reloaded.", m_changedClips.count()));
}
if (!infoLabel.isEmpty()) {
m_ui.infoLabel->setText(infoLabel);
} else {
......
......@@ -74,6 +74,7 @@ private:
QStringList m_safeImages;
QStringList m_safeFonts;
QStringList m_missingProxyIds;
QStringList m_changedClips;
void fixClipItem(QTreeWidgetItem *child, const QDomNodeList &producers, const QDomNodeList &trans);
void fixSourceClipItem(QTreeWidgetItem *child, const QDomNodeList &producers);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment