From 228b8f4201c32557c62fe1dd1a31aeba127d2e00 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Mardelle Date: Sun, 23 Nov 2008 22:35:59 +0000 Subject: [PATCH] Add recursive search when opening a document with missing clip, heavily based on a patch from Alberto Villa svn path=/branches/KDE4/; revision=2723 --- src/customtrackview.cpp | 6 +++--- src/docclipbase.cpp | 33 +++++++++++++++++++++++++++++-- src/docclipbase.h | 1 + src/kdenlivedoc.cpp | 43 +++++++++++++++++++++++++++++++++++++++++ src/kdenlivedoc.h | 2 ++ src/projectlist.cpp | 34 ++++++++++++++++++++++++++------ src/projectlist.h | 1 + src/renderer.cpp | 43 +++++++++++++++++++++++++++++++++++++++-- src/renderer.h | 4 +++- 9 files changed, 153 insertions(+), 14 deletions(-) diff --git a/src/customtrackview.cpp b/src/customtrackview.cpp index 1cfc05fd..97f32b70 100644 --- a/src/customtrackview.cpp +++ b/src/customtrackview.cpp @@ -1090,20 +1090,20 @@ void CustomTrackView::slotAddTransitionToSelectedClips(QDomElement transition) { const int transitiontrack = getPreviousVideoTrack(info.track); GenTime pos = GenTime((int)(mapToScene(m_menuPosition).x()), m_document->fps()); if (pos < item->startPos() + item->duration() / 2) { - // add transition to clip start + // add transition to clip start info.startPos = item->startPos(); if (transitiontrack != 0) transitionClip = getClipItemAt((int) info.startPos.frames(m_document->fps()), m_scene->m_tracksList.count() - transitiontrack); if (transitionClip && transitionClip->endPos() < item->endPos()) { info.endPos = transitionClip->endPos(); } else info.endPos = info.startPos + GenTime(65, m_document->fps()); } else { - // add transition to clip end + // add transition to clip end info.endPos = item->endPos(); if (transitiontrack != 0) transitionClip = getClipItemAt((int) info.endPos.frames(m_document->fps()), m_scene->m_tracksList.count() - transitiontrack); if (transitionClip && transitionClip->startPos() > item->startPos()) { info.startPos = transitionClip->startPos(); } else info.startPos = info.endPos - GenTime(65, m_document->fps()); - if (transition.attribute("tag") == "luma") EffectsList::setParameter(transition, "reverse", "1"); + if (transition.attribute("tag") == "luma") EffectsList::setParameter(transition, "reverse", "1"); } slotAddTransition(item, info, transitiontrack, transition); } diff --git a/src/docclipbase.cpp b/src/docclipbase.cpp index bf26e183..c2eae7c7 100644 --- a/src/docclipbase.cpp +++ b/src/docclipbase.cpp @@ -15,6 +15,8 @@ * * ***************************************************************************/ +#include + #include #include "kdenlivesettings.h" @@ -34,6 +36,7 @@ DocClipBase::DocClipBase(ClipManager *clipManager, QDomElement xml, const QStrin } KUrl url = KUrl(xml.attribute("resource")); + if (!m_properties.contains("file_hash") && !url.isEmpty()) getFileHash(url.path()); int out = xml.attribute("out").toInt(); if (out != 0) { setDuration(GenTime(out, KdenliveSettings::project_fps())); @@ -517,10 +520,36 @@ void DocClipBase::clearProperty(const QString &key) { m_properties.remove(key); } +void DocClipBase::getFileHash(const QString &url) { + QFile file(url); + if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file + QByteArray fileData; + QByteArray fileHash; + //kDebug() << "SETTING HASH of" << value; + m_properties.insert("file_size", QString::number(file.size())); + /* + * 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); + m_properties.insert("file_hash", QString(fileHash.toHex())); + //kDebug() << file.fileName() << file.size() << fileHash.toHex(); + } +} + void DocClipBase::setProperty(const QString &key, const QString &value) { m_properties.insert(key, value); - if (key == "resource") m_thumbProd->updateClipUrl(KUrl(value)); - else if (key == "out") setDuration(GenTime(value.toInt(), KdenliveSettings::project_fps())); + if (key == "resource") { + m_thumbProd->updateClipUrl(KUrl(value)); + getFileHash(value); + } else if (key == "out") setDuration(GenTime(value.toInt(), KdenliveSettings::project_fps())); //else if (key == "transparency") m_clipProducer->set("transparency", value.toInt()); else if (key == "colour") { char *tmp = (char *) qstrdup(value.toUtf8().data()); diff --git a/src/docclipbase.h b/src/docclipbase.h index cc427b0d..bc850231 100644 --- a/src/docclipbase.h +++ b/src/docclipbase.h @@ -221,6 +221,7 @@ private: // Private attributes void slotRefreshProducer(); void setProducerProperty(const char *name, const char *data); void setProducerProperty(const char *name, int data); + void getFileHash(const QString &url); public slots: void updateAudioThumbnail(QMap > data); diff --git a/src/kdenlivedoc.cpp b/src/kdenlivedoc.cpp index 5d7c6077..b07a4b90 100644 --- a/src/kdenlivedoc.cpp +++ b/src/kdenlivedoc.cpp @@ -1003,12 +1003,55 @@ void KdenliveDoc::addClip(QDomElement elem, QString clipId, bool createClipItem) DocClipBase *clip = m_clipManager->getClipById(producerId); if (clip == NULL) { elem.setAttribute("id", producerId); + QString path = elem.attribute("resource"); + if (!path.isEmpty() && !QFile::exists(path)) { + const QString size = elem.attribute("file_size"); + const QString hash = elem.attribute("file_hash"); + QString newpath; + KMessageBox::ButtonCode action = KMessageBox::No; + if (!size.isEmpty() && !hash.isEmpty()) { + if (!m_searchFolder.isEmpty()) newpath = Render::searchFileRecursively(m_searchFolder, size, hash); + else action = (KMessageBox::ButtonCode)KMessageBox::messageBox(kapp->activeWindow(), KMessageBox::WarningYesNoCancel, i18n("Clip %1
is invalid, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search automatically")), KGuiItem(i18n("Remove from project")), KGuiItem(i18n("Keep as placeholder"))); + } else { + newpath = KFileDialog::getOpenFileName(KUrl("kfiledialog:///clipfolder"), QString(), kapp->activeWindow(), i18n("Looking for %1", path)); + } + if (action == KMessageBox::Yes) { + kDebug() << "// ASKED FOR SRCH CLIP: " << clipId; + m_searchFolder = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow()); + if (!m_searchFolder.isEmpty()) { + newpath = Render::searchFileRecursively(QDir(m_searchFolder), size, hash); + } + } + if (!newpath.isEmpty()) { + elem.setAttribute("resource", newpath); + setNewClipResource(clipId, newpath); + } + } clip = new DocClipBase(m_clipManager, elem, producerId); m_clipManager->addClip(clip); } if (createClipItem) emit addProjectClip(clip); } +void KdenliveDoc::setNewClipResource(const QString &id, const QString &path) { + QDomNodeList prods = m_document.elementsByTagName("producer"); + int maxprod = prods.count(); + for (int i = 0; i < maxprod; i++) { + QDomNode m = prods.at(i); + QString prodId = m.toElement().attribute("id"); + if (prodId == id || prodId.startsWith(id + "_")) { + QDomNodeList params = m.childNodes(); + for (int j = 0; j < params.count(); j++) { + QDomElement e = params.item(j).toElement(); + if (e.attribute("name") == "resource") { + e.firstChild().setNodeValue(path); + break; + } + } + } + } +} + void KdenliveDoc::addClipInfo(QDomElement elem, QString clipId) { DocClipBase *clip = m_clipManager->getClipById(clipId); if (clip == NULL) { diff --git a/src/kdenlivedoc.h b/src/kdenlivedoc.h index cae88439..ef918b48 100644 --- a/src/kdenlivedoc.h +++ b/src/kdenlivedoc.h @@ -123,6 +123,7 @@ private: MltVideoProfile m_profile; QString m_scenelist; QTimer *m_autoSaveTimer; + QString m_searchFolder; /** tells whether current doc has been changed since last save event */ bool m_modified; /** Project folder, used to store project files (titles, effects,...) */ @@ -133,6 +134,7 @@ private: QDomDocument createEmptyDocument(const int videotracks, const int audiotracks); QString colorToString(const QColor& c); void checkProjectClips(); + void setNewClipResource(const QString &id, const QString &path); public slots: void slotCreateTextClip(QString group, const QString &groupId); diff --git a/src/projectlist.cpp b/src/projectlist.cpp index ffd93a24..eafc67de 100644 --- a/src/projectlist.cpp +++ b/src/projectlist.cpp @@ -442,13 +442,35 @@ void ProjectList::slotAddClip(QUrl givenUrl, QString group) { void ProjectList::slotRemoveInvalidClip(const QString &id) { ProjectItem *item = getItemById(id); if (item) { - QString path = item->referencedClip()->fileURL().path(); - if (!path.isEmpty()) KMessageBox::sorry(this, i18n("Clip %1
is invalid, will be removed from project.", path)); - + const QString path = item->referencedClip()->fileURL().path(); + //if (!path.isEmpty()) KMessageBox::sorry(this, i18n("Clip %1
is invalid, will be removed from project.", path)); + KMessageBox::ButtonCode action; + if (!path.isEmpty()) { + action = (KMessageBox::ButtonCode)KMessageBox::messageBox(this, KMessageBox::WarningYesNoCancel, i18n("Clip %1
is invalid, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search automatically")), KGuiItem(i18n("Remove from project")), KGuiItem(i18n("Keep as placeholder"))); + } else + action = KMessageBox::No; // then remove + if (action == KMessageBox::Yes) { // search + QString foundFileName; + if (!item->referencedClip()->getProperty("file_size").isEmpty() && !item->referencedClip()->getProperty("file_hash").isEmpty()) { // both hash and file size were registered + QString rootDir = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), this); + if (!rootDir.isEmpty()) { + foundFileName = Render::searchFileRecursively(QDir(rootDir), item->referencedClip()->getProperty("file_size"), item->referencedClip()->getProperty("file_hash")); + } + } + if (foundFileName.isEmpty()) + KMessageBox::sorry(this, i18n("Cannot find a match for clip
%1,
leaving in project as a placeholder.", path)); + else { + QMap properties; + properties["resource"] = foundFileName; + kDebug() << "CLIP ID:" << item->referencedClip()->getId() << "--- setting 'resource' to" << foundFileName; + slotUpdateClipProperties(item->referencedClip()->getId(), properties); + } + } else if (action == KMessageBox::No) { // remove + QList ids; + ids << id; + m_doc->deleteProjectClip(ids); + } // else keep it (last choice to be automatically bound to ESC) } - QList ids; - ids << id; - m_doc->deleteProjectClip(ids); if (!m_infoQueue.isEmpty()) QTimer::singleShot(300, this, SLOT(slotProcessNextClipInQueue())); } diff --git a/src/projectlist.h b/src/projectlist.h index 8130148f..b6115798 100644 --- a/src/projectlist.h +++ b/src/projectlist.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include diff --git a/src/renderer.cpp b/src/renderer.cpp index 4a913797..c217bd12 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -33,7 +33,8 @@ extern "C" { #include #include #include -#include +//#include +#include #include #include @@ -520,7 +521,7 @@ void Render::getFileProperties(const QDomElement &xml, const QString &clipId) { } if (xml.hasAttribute("out")) producer->set_in_and_out(xml.attribute("in").toInt(), xml.attribute("out").toInt()); - if (producer->is_blank()) { + if (producer->is_blank() || !producer->is_valid()) { kDebug() << " / / / / / / / /ERRROR / / / / // CANNOT LOAD PRODUCER: "; emit removeInvalidClip(clipId); return; @@ -691,6 +692,44 @@ void Render::getFileProperties(const QDomElement &xml, const QString &clipId) { } +//static + +QString Render::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) { + 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 (file.open(QIODevice::ReadOnly)) { + if (QString::number(file.size()) == matchSize) { + /* + * 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(fileHash.toHex()) == matchHash) + return file.fileName(); + } + } + kDebug() << 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); + if (!foundFileName.isEmpty()) + break; + } + return foundFileName; +} + /** Create the producer from the Westley QDomDocument */ #if 0 void Render::initSceneList() { diff --git a/src/renderer.h b/src/renderer.h index cc8cbde2..7eb6c5de 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -22,7 +22,8 @@ #include #include #include -#include +#include +//#include #include @@ -179,6 +180,7 @@ Q_OBJECT public: int mltChangeClipSpeed(ItemInfo info, double speed, double oldspeed, Mlt::Producer *prod); QList producersList(); + static QString searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash); private: // Private attributes & methods /** The name of this renderer - useful to identify the renderes by what they do - e.g. background rendering, workspace monitor, etc... */ -- 2.39.2