Commit 97065379 authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

On project load, detect possible corruptions related to same track...

On project load, detect possible corruptions related to same track transitions, fix them if possible and log changed / problems in project notes
parent 60a6cc96
Pipeline #88888 passed with stage
in 9 minutes and 13 seconds
......@@ -58,6 +58,7 @@ void NotesPlugin::setProject(KdenliveDoc *document)
void NotesPlugin::showDock()
{
m_notesDock->show();
m_notesDock->raise();
}
void NotesPlugin::slotInsertTimecode()
......
......@@ -616,7 +616,6 @@ void ProjectManager::doOpenFile(const QUrl &url, KAutoSaveFile *stale)
// Set default target tracks to upper audio / lower video tracks
m_project = doc;
doc->loadDocumentGuides();
if (!updateTimeline(m_project->getDocumentProperty(QStringLiteral("position")).toInt())) {
delete m_progressDialog;
m_progressDialog = nullptr;
......@@ -778,7 +777,6 @@ QString ProjectManager::documentNotes() const
void ProjectManager::slotAddProjectNote()
{
m_notesPlugin->showDock();
m_notesPlugin->widget()->raise();
m_notesPlugin->widget()->setFocus();
m_notesPlugin->widget()->addProjectNote();
}
......@@ -786,7 +784,6 @@ void ProjectManager::slotAddProjectNote()
void ProjectManager::slotAddTextNote(const QString &text)
{
m_notesPlugin->showDock();
m_notesPlugin->widget()->raise();
m_notesPlugin->widget()->setFocus();
m_notesPlugin->widget()->addTextNote(text);
}
......@@ -932,9 +929,8 @@ void ProjectManager::slotMoveFinished(KJob *job)
}
}
bool ProjectManager::updateTimeline(int pos, int scrollPos)
bool ProjectManager::updateTimeline(int pos)
{
Q_UNUSED(scrollPos);
pCore->taskManager.slotCancelJobs();
pCore->window()->getMainTimeline()->loading = true;
pCore->window()->slotSwitchTimelineZone(m_project->getDocumentProperty(QStringLiteral("enableTimelineZone")).toInt() == 1);
......@@ -963,7 +959,8 @@ bool ProjectManager::updateTimeline(int pos, int scrollPos)
// Add snap point at projec start
m_mainTimelineModel->addSnap(0);
pCore->window()->getMainTimeline()->setModel(m_mainTimelineModel, pCore->monitorManager()->projectMonitor()->getControllerProxy());
if (!constructTimelineFromMelt(m_mainTimelineModel, tractor, m_progressDialog, m_project->modifiedDecimalPoint())) {
bool projectErrors = false;
if (!constructTimelineFromMelt(m_mainTimelineModel, tractor, m_progressDialog, m_project->modifiedDecimalPoint(), &projectErrors)) {
//TODO: act on project load failure
qDebug()<<"// Project failed to load!!";
}
......@@ -999,7 +996,11 @@ bool ProjectManager::updateTimeline(int pos, int scrollPos)
// Reset locale to C to ensure numbers are serialised correctly
LocaleHandling::resetLocale();
if (projectErrors) {
m_notesPlugin->showDock();
m_notesPlugin->widget()->raise();
m_notesPlugin->widget()->setFocus();
}
return true;
}
......
......@@ -185,7 +185,7 @@ signals:
protected:
/** @brief Update the timeline according to the MLT XML */
bool updateTimeline(int pos = -1, int scrollPos = -1);
bool updateTimeline(int pos);
private:
/** @brief checks if autoback files exists, recovers from it if user says yes, returns true if files were recovered. */
......
......@@ -26,21 +26,24 @@
#include <mlt++/MltField.h>
#include <mlt++/MltTransition.h>
#include <QApplication>
#include <project/projectmanager.h>
static QStringList m_errorMessage;
static QStringList m_notesLog;
bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, int tid, Mlt::Tractor &track,
const std::unordered_map<QString, QString> &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QString originalDecimalPoint, QProgressDialog *progressDialog = nullptr);
bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, int tid, Mlt::Playlist &track,
const std::unordered_map<QString, QString> &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QString originalDecimalPoint, int playlist, QProgressDialog *progressDialog = nullptr);
const std::unordered_map<QString, QString> &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QString originalDecimalPoint, int playlist, QList<Mlt::Transition *> compositions, QProgressDialog *progressDialog = nullptr);
bool constructTimelineFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, Mlt::Tractor tractor, QProgressDialog *progressDialog, QString originalDecimalPoint)
bool constructTimelineFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, Mlt::Tractor tractor, QProgressDialog *progressDialog, QString originalDecimalPoint, bool *projectErrors)
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
// First, we destruct the previous tracks
timeline->requestReset(undo, redo);
m_errorMessage.clear();
m_notesLog.clear();
std::unordered_map<QString, QString> binIdCorresp;
QStringList expandedFolders;
pCore->projectItemModel()->loadBinPlaylist(&tractor, timeline->tractor(), binIdCorresp, expandedFolders, progressDialog);
......@@ -108,7 +111,7 @@ bool constructTimelineFromMelt(const std::shared_ptr<TimelineItemModel> &timelin
timeline->setTrackProperty(tid, QStringLiteral("hide"), QString::number(muteState));
}
ok = ok && constructTrackFromMelt(timeline, tid, local_playlist, binIdCorresp, undo, redo, audioTrack, originalDecimalPoint, 0, progressDialog);
ok = ok && constructTrackFromMelt(timeline, tid, local_playlist, binIdCorresp, undo, redo, audioTrack, originalDecimalPoint, 0, QList<Mlt::Transition *> (), progressDialog);
if (local_playlist.get_int("kdenlive:locked_track") > 0) {
lockedTracksIndexes << tid;
}
......@@ -154,7 +157,15 @@ bool constructTimelineFromMelt(const std::shared_ptr<TimelineItemModel> &timelin
QString id(t->get("kdenlive_id"));
int compoId;
int aTrack = t->get_a_track();
if (!timeline->isTrack(t->get_b_track() - 1)) {
QString tcInfo = QString("<a href=\"%1\">%2</a>").arg(QString::number(t->get_in()), pCore->timecode().getTimecodeFromFrames(t->get_in()));
m_notesLog << i18n("%1 Composition (%2) with invalid track reference found and removed.", tcInfo, t->get("id"));
continue;
}
if (aTrack > tractor.count()) {
int tid = timeline->getTrackIndexFromPosition(t->get_b_track() - 1);
QString tcInfo = QString("<a href=\"%1?%2\">%3 %4</a>").arg(QString::number(t->get_in()), QString::number(timeline->getTrackPosition(tid)+1), timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(t->get_in()));
m_notesLog << i18n("%1 Composition (%2) with invalid track reference found and removed.", tcInfo, t->get("id"));
m_errorMessage << i18n("Invalid composition %1 found on track %2 at %3, compositing with track %4.", t->get("id"), t->get_b_track(),
t->get_in(), t->get_a_track());
continue;
......@@ -164,6 +175,9 @@ bool constructTimelineFromMelt(const std::shared_ptr<TimelineItemModel> &timelin
int pos = videoTracksIndexes.indexOf(t->get_b_track());
if (pos > 0 && videoTracksIndexes.at(pos - 1) != aTrack) {
t->set("force_track", 1);
int tid = timeline->getTrackIndexFromPosition(t->get_b_track() - 1);
QString tcInfo = QString("<a href=\"%1?%2\">%3 %4</a>").arg(QString::number(t->get_in()), QString::number(timeline->getTrackPosition(tid)+1), timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(t->get_in()));
m_notesLog << i18n("%1 Composition was not applied on expected track, manually enforce the track.", tcInfo);
m_errorMessage << i18n("Incorrect composition %1 found on track %2 at %3, compositing with track %4 was set to forced track.", t->get("id"), t->get_b_track(),
t->get_in(), t->get_a_track());
}
......@@ -172,10 +186,14 @@ bool constructTimelineFromMelt(const std::shared_ptr<TimelineItemModel> &timelin
compositionOk = timeline->requestCompositionInsertion(id, timeline->getTrackIndexFromPosition(t->get_b_track() - 1), t->get_a_track(), t->get_in(), t->get_length(), std::move(transProps), compoId, undo, redo, false, originalDecimalPoint);
if (!compositionOk) {
// timeline->requestItemDeletion(compoId, false);
int tid = timeline->getTrackIndexFromPosition(t->get_b_track() - 1);
QString tcInfo = QString("<a href=\"%1?%2\">%3 %4</a>").arg(QString::number(t->get_in()), QString::number(timeline->getTrackPosition(tid)+1), timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(t->get_in()));
m_notesLog << i18n("%1 Invalid composition found and removed.", tcInfo);
m_errorMessage << i18n("Invalid composition %1 found on track %2 at %3.", t->get("id"), t->get_b_track(), t->get_in());
continue;
}
}
qDeleteAll(compositions);
// build internal track compositing
timeline->buildTrackCompositing();
......@@ -191,7 +209,14 @@ bool constructTimelineFromMelt(const std::shared_ptr<TimelineItemModel> &timelin
undo();
return false;
}
if (!m_errorMessage.isEmpty()) {
if (!m_notesLog.isEmpty()) {
m_notesLog.prepend(i18n("Errors found when opening project file (%1)", QDateTime::currentDateTime().toString()));
pCore->projectManager()->slotAddTextNote(m_notesLog.join("<br/>"));
if (projectErrors) {
*projectErrors = true;
}
KMessageBox::detailedSorry(qApp->activeWindow(), i18n("Some errors were detected in the project file.\nThe project was modified to fix the conflicts. Changes made to the project have been listed in the Project Notes tab,\nplease review them to ensure your project integrity."), m_errorMessage.join("\n"), i18n("Problems found in your project file"));
} else if (!m_errorMessage.isEmpty()) {
KMessageBox::sorry(qApp->activeWindow(), m_errorMessage.join("\n"), i18n("Problems found in your project file"));
}
return true;
......@@ -227,7 +252,7 @@ bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline,
return false;
}
Mlt::Playlist playlist(*sub_track);
constructTrackFromMelt(timeline, tid, playlist, binIdCorresp, undo, redo, audioTrack, originalDecimalPoint, i, progressDialog);
constructTrackFromMelt(timeline, tid, playlist, binIdCorresp, undo, redo, audioTrack, originalDecimalPoint, i, compositions, progressDialog);
if (i == 0) {
// Pass track properties
int height = track.get_int("kdenlive:trackheight");
......@@ -291,7 +316,7 @@ PlaylistState::ClipState inferState(const std::shared_ptr<Mlt::Producer> &prod,
} // namespace
bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, int tid, Mlt::Playlist &track,
const std::unordered_map<QString, QString> &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QString originalDecimalPoint, int playlist, QProgressDialog *progressDialog)
const std::unordered_map<QString, QString> &binIdCorresp, Fun &undo, Fun &redo, bool audioTrack, QString originalDecimalPoint, int playlist, QList<Mlt::Transition *> compositions, QProgressDialog *progressDialog)
{
int max = track.count();
for (int i = 0; i < max; i++) {
......@@ -328,10 +353,13 @@ bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline,
// Project was somehow corrupted
qWarning() << "can't find clip with id: " << clipId << "in bin playlist";
QStringList fixedId = pCore->projectItemModel()->getClipByUrl(QFileInfo(clip->parent().get("resource")));
QString tcInfo = QString("<a href=\"%1?%2\">%3 %4</a>").arg(QString::number(position), QString::number(timeline->getTrackPosition(tid)+1), timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(position));
if (!fixedId.isEmpty()) {
binId = fixedId.first();
m_notesLog << i18n("%1 Timeline clip (%2) with incorrect bin reference found and recovered.", tcInfo, clip->parent().get("id"));
m_errorMessage << i18n("Invalid clip %1 (%2) not found in project bin, recovered.", clip->parent().get("id"), clipId);
} else {
m_notesLog << i18n("%1 Timeline clip (%2) without bin refrence found and removed.", tcInfo, clip->parent().get("id"));
m_errorMessage << i18n("Project corrupted. Clip %1 (%2) not found in project bin.", clip->parent().get("id"), clipId);
// Do not try to insert clip
continue;
......@@ -347,6 +375,84 @@ bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline,
int cid = -1;
if (pCore->bin()->getBinClip(binId)) {
PlaylistState::ClipState st = inferState(clip, audioTrack);
if (playlist > 0) {
// Clips on playlist > 0 must have a mix or something is wrong
bool hasStartMix = !timeline->trackIsBlankAt(tid, position, 0);
int duration = clip->get_playtime() - 1;
bool hasEndMix = !timeline->trackIsBlankAt(tid, position + duration, 0);
bool startMixToFind = hasStartMix;
bool endMixToFind = hasEndMix;
if (startMixToFind || endMixToFind) {
for (auto &compo : compositions) {
int in = compo->get_in();
int out = compo->get_out();
if (startMixToFind && out > position && in <= position) {
startMixToFind = false;
}
if (endMixToFind && in < position + duration && out >= position + duration) {
endMixToFind = false;
}
if (!startMixToFind && !endMixToFind) {
break;
}
}
if (startMixToFind || endMixToFind) {
// A mix for this clip is missing
QString tcInfo = QString("<a href=\"%1?%2\">%3 %4</a>").arg(QString::number(position), QString::number(timeline->getTrackPosition(tid)+1), timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(position));
// Try to resize clip and put it on playlist 0
if (hasEndMix && endMixToFind) {
// Find last empty frame on playlist 0
int lastAvailableFrame = timeline->getClipStartAt(tid, position + duration, 0);
if (lastAvailableFrame > position) {
// Resize clip to fit.
int updatedDuration = lastAvailableFrame - position - 1;
int currentIn = clip->get_in();
clip->set_in_and_out(currentIn, currentIn + updatedDuration);
hasEndMix = false;
if (!startMixToFind) {
// Move to top playlist
cid = ClipModel::construct(timeline, binId, clip, st, tid, originalDecimalPoint, hasStartMix ? playlist : 0);
ok = timeline->requestClipMove(cid, tid, position, true, true, false, true, undo, redo);
m_notesLog << i18n("%1 Clip (%2) with missing mix found and resized", tcInfo, clip->parent().get("id"));
m_errorMessage << i18n("Clip without mix %1 found and resized on track %2 at %3.", clip->parent().get("id"), timeline->getTrackTagById(tid), position);
continue;
}
} else {
m_errorMessage << i18n("Invalid clip without mix %1 found and removed on track %2 at %3.", clip->parent().get("id"), timeline->getTrackTagById(tid), position);
m_notesLog << i18n("%1 Clip (%2) with missing mix found and removed", tcInfo, clip->parent().get("id"));
continue;
}
}
if (hasStartMix && startMixToFind) {
int firstAvailableFrame = timeline->getClipEndAt(tid, position, 0);
if (firstAvailableFrame < position + duration) {
int delta = firstAvailableFrame - position;
int currentIn = clip->get_in();
currentIn += delta;
position += delta;
int currentOut = clip->get_out();
clip->set_in_and_out(currentIn, currentOut);
// Move to top playlist
cid = ClipModel::construct(timeline, binId, clip, st, tid, originalDecimalPoint, hasEndMix ? playlist : 0);
ok = timeline->requestClipMove(cid, tid, position, true, true, false, true, undo, redo);
QString tcInfo = QString("<a href=\"%1?%2\">%3 %4</a>").arg(QString::number(position), QString::number(timeline->getTrackPosition(tid)+1), timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(position));
if (!ok && cid > -1) {
timeline->requestItemDeletion(cid, false);
m_errorMessage << i18n("Invalid clip %1 found on track %2 at %3.", clip->parent().get("id"), track.get("id"), position);
m_notesLog << i18n("%1 Invalid clip (%2) found and removed", tcInfo, clip->parent().get("id"));
continue;
}
m_errorMessage << i18n("Clip without mix %1 found and resized on track %2 at %3.", clip->parent().get("id"), timeline->getTrackTagById(tid), position);
m_notesLog << i18n("%1 Clip (%2) with missing mix found and resized", tcInfo, clip->parent().get("id"));
continue;
}
}
m_errorMessage << i18n("Invalid clip without mix %1 found and removed on track %2 at %3.", clip->parent().get("id"), timeline->getTrackTagById(tid), position);
m_notesLog << i18n("%1 Clip (%2) with missing mix found and removed", tcInfo, clip->parent().get("id"));
continue;
}
}
}
cid = ClipModel::construct(timeline, binId, clip, st, tid, originalDecimalPoint, playlist);
ok = timeline->requestClipMove(cid, tid, position, true, true, false, true, undo, redo);
} else {
......@@ -355,7 +461,9 @@ bool constructTrackFromMelt(const std::shared_ptr<TimelineItemModel> &timeline,
if (!ok && cid > -1) {
timeline->requestItemDeletion(cid, false);
m_errorMessage << i18n("Invalid clip %1 found on track %2 at %3.", clip->parent().get("id"), track.get("id"), position);
break;
QString tcInfo = QString("<a href=\"%1?%2\">%3 %4</a>").arg(QString::number(position), QString::number(timeline->getTrackPosition(tid)+1), timeline->getTrackTagById(tid), pCore->timecode().getTimecodeFromFrames(position));
m_notesLog << i18n("%1 Invalid clip (%2) found and removed", tcInfo, clip->parent().get("id"));
continue;
}
break;
}
......
......@@ -14,6 +14,6 @@ class QProgressDialog;
/** @brief This function can be used to construct a TimelineModel object from a Mlt object hierarchy
*/
bool constructTimelineFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, Mlt::Tractor mlt_timeline, QProgressDialog *progressDialog = nullptr, QString originalDecimalPoint = QString());
bool constructTimelineFromMelt(const std::shared_ptr<TimelineItemModel> &timeline, Mlt::Tractor mlt_timeline, QProgressDialog *progressDialog = nullptr, QString originalDecimalPoint = QString(), bool *projectErrors = nullptr);
#endif
......@@ -2895,6 +2895,24 @@ int TimelineModel::requestItemResizeInfo(int itemId, int in, int out, int size,
return size;
}
bool TimelineModel::trackIsBlankAt(int tid, int pos, int playlist) const
{
if (pos >= getTrackById_const(tid)->trackDuration() - 1) {
return true;
}
return getTrackById_const(tid)->isBlankAt(pos, playlist);
}
int TimelineModel::getClipStartAt(int tid, int pos, int playlist) const
{
return getTrackById_const(tid)->getClipStart(pos, playlist);
}
int TimelineModel::getClipEndAt(int tid, int pos, int playlist) const
{
return getTrackById_const(tid)->getClipEnd(pos, playlist);
}
int TimelineModel::requestItemSpeedChange(int itemId, int size, bool right, int snapDistance)
{
Q_ASSERT(isClip(itemId));
......
......@@ -755,6 +755,11 @@ public:
const QSize getCompositionSizeOnTrack(const ObjectId &id);
/** @brief Get a track tag (A1, V1, V2,...) through its id */
const QString getTrackTagById(int trackId) const;
/** @brief returns true if track is empty at position on playlist */
bool trackIsBlankAt(int tid, int pos, int playlist) const;
/** @brief returns the position of the clip start on a playlist */
int getClipStartAt(int tid, int pos, int playlist) const;
int getClipEndAt(int tid, int pos, int playlist) const;
protected:
/** @brief Register a new track. This is a call-back meant to be called from TrackModel
......
......@@ -1165,6 +1165,33 @@ int TrackModel::getBlankStart(int position)
return result;
}
int TrackModel::getClipStart(int position, int track)
{
if (track == -1) {
return getBlankStart(position);
}
READ_LOCK();
if (m_playlists[track].is_blank_at(position)) {
return position;
}
int clip_index = m_playlists[track].get_clip_index_at(position);
return m_playlists[track].clip_start(clip_index);
}
int TrackModel::getClipEnd(int position, int track)
{
if (track == -1) {
return getBlankStart(position);
}
READ_LOCK();
if (m_playlists[track].is_blank_at(position)) {
return position;
}
int clip_index = m_playlists[track].get_clip_index_at(position);
clip_index++;
return m_playlists[track].clip_start(clip_index);
}
int TrackModel::getBlankStart(int position, int track)
{
if (track == -1) {
......
......@@ -217,6 +217,9 @@ protected:
/** @brief Returns the start of the blank on a specific playlist */
int getBlankStart(int position, int track);
int getBlankSizeAtPos(int frame);
/** @brief Returns the start of the clip on a specific playlist */
int getClipStart(int position, int track);
int getClipEnd(int position, int track);
/** @brief Returns true if clip at position is the last on playlist
* @param position the position in playlist
*/
......
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