mirror of
https://invent.kde.org/multimedia/kdenlive
synced 2025-12-08 01:09:59 +01:00
Compare commits
1 Commits
work/embed
...
work/proje
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13adeaffa7 |
@@ -1826,14 +1826,16 @@ Fun ProjectItemModel::removeProjectItem_lambda(int binId, int id)
|
||||
};
|
||||
}
|
||||
|
||||
void ProjectItemModel::checkSequenceIntegrity(const QString activeSequenceId)
|
||||
const QMap<QString, QByteArray> ProjectItemModel::checkSequenceIntegrity(const QString activeSequenceId)
|
||||
{
|
||||
QStringList sequencesIds = pCore->currentDoc()->getTimelinesIds();
|
||||
const QMap<QString, QByteArray> hash = pCore->currentDoc()->getTimelinesHash();
|
||||
Q_ASSERT(sequencesIds.contains(activeSequenceId));
|
||||
QStringList allMltIds = m_binPlaylist->getAllMltIds();
|
||||
for (auto &i : sequencesIds) {
|
||||
Q_ASSERT(allMltIds.contains(i));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::shared_ptr<EffectStackModel> ProjectItemModel::getClipEffectStack(int itemId)
|
||||
|
||||
@@ -254,7 +254,7 @@ public:
|
||||
/** @brief Remove clip references for a timeline. */
|
||||
void removeReferencedClips(const QUuid &uuid, bool onDeletion);
|
||||
/** @brief Check that all sequences are correctly stored in the model */
|
||||
void checkSequenceIntegrity(const QString activeSequenceId);
|
||||
const QMap<QString, QByteArray> checkSequenceIntegrity(const QString activeSequenceId);
|
||||
std::shared_ptr<EffectStackModel> getClipEffectStack(int itemId);
|
||||
|
||||
protected:
|
||||
|
||||
@@ -2210,6 +2210,19 @@ QStringList KdenliveDoc::getTimelinesIds()
|
||||
return ids;
|
||||
}
|
||||
|
||||
const QMap<QString, QByteArray> KdenliveDoc::getTimelinesHash()
|
||||
{
|
||||
QMap<QString, QByteArray> hash;
|
||||
QMapIterator<QUuid, std::shared_ptr<TimelineItemModel>> j(m_timelines);
|
||||
while (j.hasNext()) {
|
||||
j.next();
|
||||
const QString tId(j.value()->tractor()->get("id"));
|
||||
hash.insert(tId, j.value()->timelineHash());
|
||||
qDebug() << "====== INSERTING TIMELINE HASH:\n" << tId << " = " << hash.value(tId) << "\n\nHHHHHHHHHHHHHHHHHHHHH";
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
void KdenliveDoc::addTimeline(const QUuid &uuid, std::shared_ptr<TimelineItemModel> model, bool force)
|
||||
{
|
||||
if (force && m_timelines.find(uuid) != m_timelines.end()) {
|
||||
|
||||
@@ -301,6 +301,8 @@ public:
|
||||
QList<QUuid> getTimelinesUuids() const;
|
||||
/** @brief Return all timelines MLT ids.*/
|
||||
QStringList getTimelinesIds();
|
||||
/** @brief Returns a timeline hash to check project integrity.*/
|
||||
const QMap<QString, QByteArray> getTimelinesHash();
|
||||
/** @brief Returns the number of timelines in this project.*/
|
||||
int openedTimelineCount() const;
|
||||
/** @brief Get the currently active project name.*/
|
||||
|
||||
@@ -63,6 +63,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
#include <QSaveFile>
|
||||
#include <QTimeZone>
|
||||
#include <QUndoGroup>
|
||||
#include <QtConcurrent>
|
||||
|
||||
static QString getProjectNameFilters(bool ark = true)
|
||||
{
|
||||
@@ -540,7 +541,7 @@ bool ProjectManager::saveFileAs(const QString &outputFileName, bool saveOverExis
|
||||
prepareSave();
|
||||
QString saveFolder = QFileInfo(outputFileName).absolutePath();
|
||||
m_project->updateWorkFilesBeforeSave(outputFileName);
|
||||
checkProjectIntegrity();
|
||||
const QMap<QString, QByteArray> hash = checkProjectIntegrity();
|
||||
QString scene = projectSceneList(saveFolder);
|
||||
if (!m_replacementPattern.isEmpty()) {
|
||||
QMapIterator<QString, QString> i(m_replacementPattern);
|
||||
@@ -554,6 +555,14 @@ bool ProjectManager::saveFileAs(const QString &outputFileName, bool saveOverExis
|
||||
KNotification::event(QStringLiteral("ErrorMessage"), i18n("Saving project file <br><b>%1</B> failed", outputFileName), QPixmap());
|
||||
return false;
|
||||
}
|
||||
// Check the newly saved project file against our current hash
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
m_integrityJob = QtConcurrent::run(this, &ProjectManager::checkFileIntegrity, outputFileName, hash);
|
||||
#else
|
||||
m_integrityJob = QtConcurrent::run(&ProjectManager::checkFileIntegrity, this, outputFileName, hash);
|
||||
#endif
|
||||
m_watcher.setFuture(m_integrityJob);
|
||||
scene.clear();
|
||||
QUrl url = QUrl::fromLocalFile(outputFileName);
|
||||
// Save timeline thumbnails
|
||||
std::unordered_map<QString, std::vector<int>> thumbKeys = pCore->window()->getCurrentTimeline()->controller()->getThumbKeys();
|
||||
@@ -2237,11 +2246,11 @@ void ProjectManager::replaceTimelineInstances(const QString &sourceId, const QSt
|
||||
m_activeTimelineModel->processTimelineReplacement(instances, sourceId, replacementId, maxDuration, replaceAudio, replaceVideo);
|
||||
}
|
||||
|
||||
void ProjectManager::checkProjectIntegrity()
|
||||
const QMap<QString, QByteArray> ProjectManager::checkProjectIntegrity()
|
||||
{
|
||||
// Ensure the active timeline sequence is correctly inserted in the main_bin playlist
|
||||
const QString activeSequenceId(m_activeTimelineModel->tractor()->get("id"));
|
||||
pCore->projectItemModel()->checkSequenceIntegrity(activeSequenceId);
|
||||
return pCore->projectItemModel()->checkSequenceIntegrity(activeSequenceId);
|
||||
}
|
||||
|
||||
void ProjectManager::handleLog(const QString &message)
|
||||
@@ -2253,3 +2262,61 @@ void ProjectManager::showTrackEffectStack(int tid)
|
||||
{
|
||||
m_activeTimelineModel->showTrackEffectStack(tid);
|
||||
}
|
||||
|
||||
bool ProjectManager::checkFileIntegrity(const QString outFile, const QMap<QString, QByteArray> hash)
|
||||
{
|
||||
QDomDocument doc;
|
||||
QFile file(outFile);
|
||||
if (!file.open(QIODevice::ReadOnly)) return false;
|
||||
if (!doc.setContent(&file)) {
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
file.close();
|
||||
|
||||
// Find elements
|
||||
QDomNodeList tractors = doc.documentElement().elementsByTagName(QStringLiteral("tractor"));
|
||||
QDomNodeList playlists = doc.documentElement().elementsByTagName(QStringLiteral("playlist"));
|
||||
for (int j = 0; j < tractors.count(); j++) {
|
||||
QDomElement tractor = tractors.item(j).toElement();
|
||||
const QString tId = tractor.attribute(QStringLiteral("id"));
|
||||
if (hash.contains(tId)) {
|
||||
// Calculate item hash
|
||||
QDomNodeList tracks = tractor.elementsByTagName(QStringLiteral("track"));
|
||||
QVector<QString> trackIds;
|
||||
// Parse tracks (skip the first black background track)
|
||||
for (int k = 1; k < tracks.count(); k++) {
|
||||
// Get this sequence's track names
|
||||
trackIds << tracks.item(k).toElement().attribute("producer");
|
||||
}
|
||||
for (int k = 0; k < tractors.count(); k++) {
|
||||
QDomElement subtractor = tractors.item(j).toElement();
|
||||
const QString subTid = subtractor.attribute(QStringLiteral("id"));
|
||||
if (trackIds.contains(subTid)) {
|
||||
// We found a sequence track, get its 2 playlist ids
|
||||
QDomNodeList playlistItems = subtractor.elementsByTagName(QStringLiteral("track"));
|
||||
QVector<QString> playlistIds;
|
||||
for (int l = 0; l < playlistItems.count(); l++) {
|
||||
playlistIds << playlistItems.item(l).toElement().attribute("producer");
|
||||
}
|
||||
// Ok now we have the 2 playlist for a track, we can process the hash
|
||||
for (int m = 0; m < playlists.count(); m++) {
|
||||
QDomElement playlist = playlists.item(m).toElement();
|
||||
const QString plId = playlist.attribute(QStringLiteral("id"));
|
||||
if (playlistIds.contains(plId)) {
|
||||
// Get hash for this one
|
||||
int pos = 0;
|
||||
QDomNodeList items = playlist.childNodes();
|
||||
for (int n = 0; n < items.count(); n++) {
|
||||
// Handle blanks
|
||||
|
||||
// Handle clips
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
#include "kdenlivecore_export.h"
|
||||
#include <KRecentFilesAction>
|
||||
#include <QDir>
|
||||
#include <QElapsedTimer>
|
||||
#include <QFutureWatcher>
|
||||
#include <QObject>
|
||||
#include <QTime>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#include "timeline2/model/timelineitemmodel.hpp"
|
||||
|
||||
@@ -258,6 +259,8 @@ private:
|
||||
QUrl m_startUrl;
|
||||
QString m_loadClipsOnOpen;
|
||||
QMap<QString, QString> m_replacementPattern;
|
||||
QFutureWatcher<void> m_watcher;
|
||||
QFuture<void> m_integrityJob;
|
||||
|
||||
QAction *m_fileRevert;
|
||||
KRecentFilesAction *m_recentFilesAction;
|
||||
@@ -272,7 +275,9 @@ private:
|
||||
void passSequenceProperties(const QUuid &uuid, std::shared_ptr<Mlt::Producer> prod, Mlt::Tractor tractor, std::shared_ptr<TimelineItemModel> timelineModel,
|
||||
TimelineWidget *timelineWidget);
|
||||
/** @brief Ensure sequences are correctly stored in our project model */
|
||||
void checkProjectIntegrity();
|
||||
const QMap<QString, QByteArray> checkProjectIntegrity();
|
||||
/** @brief Ensure the saved project matches out current state */
|
||||
bool checkFileIntegrity(const QString outFile, const QMap<QString, QByteArray> hash);
|
||||
/** @brief Opening a project file failed, propose to open a backup */
|
||||
void abortProjectLoad(const QUrl &url);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user