1 /***************************************************************************
2 * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
21 #include "documentchecker.h"
23 #include "definitions.h"
24 #include "kdenlivesettings.h"
27 #include <KGlobalSettings>
29 #include <KIO/NetAccess>
30 #include <KFileDialog>
31 #include <KApplication>
32 #include <KUrlRequesterDialog>
33 #include <KMessageBox>
35 #include <QTreeWidgetItem>
37 #include <QHeaderView>
41 #include <QCryptographicHash>
43 const int hashRole = Qt::UserRole;
44 const int sizeRole = Qt::UserRole + 1;
45 const int idRole = Qt::UserRole + 2;
46 const int statusRole = Qt::UserRole + 3;
48 const int CLIPMISSING = 0;
50 const int CLIPPLACEHOLDER = 2;
51 const int LUMAMISSING = 10;
52 const int LUMAOK = 11;
53 const int LUMAPLACEHOLDER = 12;
55 DocumentChecker::DocumentChecker(QList <QDomElement> missingClips, QDomDocument doc, QWidget * parent) :
59 setFont(KGlobalSettings::toolBarFont());
62 QStringList missingLumas;
63 QDomNodeList trans = doc.elementsByTagName("transition");
64 for (int i = 0; i < trans.count(); i++) {
65 QString luma = getProperty(trans.at(i).toElement(), "luma");
66 if (!luma.isEmpty() && !QFile::exists(luma)) {
67 if (!missingLumas.contains(luma)) {
68 missingLumas.append(luma);
69 QTreeWidgetItem *item = new QTreeWidgetItem(treeWidget, QStringList() << i18n("Luma file") << luma);
70 item->setIcon(0, KIcon("dialog-close"));
71 item->setData(0, idRole, luma);
72 item->setData(0, statusRole, LUMAMISSING);
77 buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
78 for (int i = 0; i < missingClips.count(); i++) {
79 e = missingClips.at(i).toElement();
81 switch (e.attribute("type").toInt()) {
83 clipType = i18n("Video clip");
86 clipType = i18n("Mute video clip");
89 clipType = i18n("Audio clip");
92 clipType = i18n("Playlist clip");
95 clipType = i18n("Image clip");
98 clipType = i18n("Slideshow clip");
101 clipType = i18n("Video clip");
103 QTreeWidgetItem *item = new QTreeWidgetItem(treeWidget, QStringList() << clipType << e.attribute("resource"));
104 item->setIcon(0, KIcon("dialog-close"));
105 item->setData(0, hashRole, e.attribute("file_hash"));
106 item->setData(0, sizeRole, e.attribute("file_size"));
107 item->setData(0, idRole, e.attribute("id"));
108 item->setData(0, statusRole, CLIPMISSING);
110 connect(recursiveSearch, SIGNAL(pressed()), this, SLOT(slotSearchClips()));
111 connect(usePlaceholders, SIGNAL(pressed()), this, SLOT(slotPlaceholders()));
112 connect(removeSelected, SIGNAL(pressed()), this, SLOT(slotDeleteSelected()));
113 connect(treeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotEditItem(QTreeWidgetItem *, int)));
117 DocumentChecker::~DocumentChecker() {}
120 QString DocumentChecker::getProperty(QDomElement effect, const QString &name)
122 QDomNodeList params = effect.elementsByTagName("property");
123 for (int i = 0; i < params.count(); i++) {
124 QDomElement e = params.item(i).toElement();
125 if (e.attribute("name") == name) {
126 return e.firstChild().nodeValue();
132 void DocumentChecker::setProperty(QDomElement effect, const QString &name, const QString value)
134 QDomNodeList params = effect.elementsByTagName("property");
135 for (int i = 0; i < params.count(); i++) {
136 QDomElement e = params.item(i).toElement();
137 if (e.attribute("name") == name) {
138 e.firstChild().setNodeValue(value);
143 void DocumentChecker::slotSearchClips()
145 QString newpath = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow(), i18n("Clips folder"));
146 if (newpath.isEmpty()) return;
148 recursiveSearch->setEnabled(false);
149 QTreeWidgetItem *child = treeWidget->topLevelItem(ix);
151 if (child->data(0, statusRole).toInt() == CLIPMISSING) {
152 QString clipPath = searchFileRecursively(QDir(newpath), child->data(0, sizeRole).toString(), child->data(0, hashRole).toString());
153 if (!clipPath.isEmpty()) {
154 child->setText(1, clipPath);
155 child->setIcon(0, KIcon("dialog-ok"));
156 child->setData(0, statusRole, CLIPOK);
158 } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
159 QString fileName = searchLuma(child->data(0, idRole).toString());
160 if (!fileName.isEmpty()) {
161 child->setText(1, fileName);
162 child->setIcon(0, KIcon("dialog-ok"));
163 child->setData(0, statusRole, LUMAOK);
167 child = treeWidget->topLevelItem(ix);
169 recursiveSearch->setEnabled(true);
174 QString DocumentChecker::searchLuma(QString file) const
176 KUrl searchPath(KdenliveSettings::mltpath());
177 if (file.contains("PAL"))
178 searchPath.cd("../lumas/PAL");
180 searchPath.cd("../lumas/NTSC");
181 QString result = searchPath.path(KUrl::AddTrailingSlash) + KUrl(file).fileName();
182 if (QFile::exists(result))
188 QString DocumentChecker::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const
190 QString foundFileName;
193 QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable);
194 for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
195 QFile file(dir.absoluteFilePath(filesAndDirs.at(i)));
196 if (QString::number(file.size()) == matchSize) {
197 if (file.open(QIODevice::ReadOnly)) {
199 * 1 MB = 1 second per 450 files (or faster)
200 * 10 MB = 9 seconds per 450 files (or faster)
202 if (file.size() > 1000000*2) {
203 fileData = file.read(1000000);
204 if (file.seek(file.size() - 1000000))
205 fileData.append(file.readAll());
207 fileData = file.readAll();
209 fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
210 if (QString(fileHash.toHex()) == matchHash)
211 return file.fileName();
214 //kDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex();
216 filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
217 for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
218 foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash);
219 if (!foundFileName.isEmpty())
222 return foundFileName;
225 void DocumentChecker::slotEditItem(QTreeWidgetItem *item, int)
227 KUrl url = KUrlRequesterDialog::getUrl(item->text(1), this, i18n("Enter new location for file"));
228 if (url.isEmpty()) return;
229 item->setText(1, url.path());
230 if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) {
231 item->setIcon(0, KIcon("dialog-ok"));
232 int id = item->data(0, statusRole).toInt();
233 if (id < 10) item->setData(0, statusRole, CLIPOK);
234 else item->setData(0, statusRole, LUMAOK);
240 void DocumentChecker::accept()
242 QDomElement e, property;
243 QDomNodeList producers = m_doc.elementsByTagName("producer");
244 QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
245 QDomNodeList properties;
248 // prepare transitions
249 QDomNodeList trans = m_doc.elementsByTagName("transition");
251 QTreeWidgetItem *child = treeWidget->topLevelItem(ix);
253 if (child->data(0, statusRole).toInt() == CLIPOK) {
254 QString id = child->data(0, idRole).toString();
255 for (int i = 0; i < infoproducers.count(); i++) {
256 e = infoproducers.item(i).toElement();
257 if (e.attribute("id") == id) {
259 e.setAttribute("resource", child->text(1));
260 e.setAttribute("name", KUrl(child->text(1)).fileName());
264 for (int i = 0; i < producers.count(); i++) {
265 e = producers.item(i).toElement();
266 if (e.attribute("id").section('_', 0, 0) == id) {
268 properties = e.childNodes();
269 for (int j = 0; j < properties.count(); ++j) {
270 property = properties.item(j).toElement();
271 if (property.attribute("name") == "resource") {
272 property.firstChild().setNodeValue(child->text(1));
278 } else if (child->data(0, statusRole).toInt() == CLIPPLACEHOLDER) {
279 QString id = child->data(0, idRole).toString();
280 for (int i = 0; i < infoproducers.count(); i++) {
281 e = infoproducers.item(i).toElement();
282 if (e.attribute("id") == id) {
284 e.setAttribute("placeholder", '1');
288 } else if (child->data(0, statusRole).toInt() == LUMAOK) {
289 for (int i = 0; i < trans.count(); i++) {
290 QString luma = getProperty(trans.at(i).toElement(), "luma");
291 kDebug() << "luma: " << luma;
292 if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) {
293 setProperty(trans.at(i).toElement(), "luma", child->text(1));
294 kDebug() << "replace with; " << child->text(1);
297 } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
298 for (int i = 0; i < trans.count(); i++) {
299 QString luma = getProperty(trans.at(i).toElement(), "luma");
300 if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) {
301 setProperty(trans.at(i).toElement(), "luma", QString());
306 child = treeWidget->topLevelItem(ix);
311 void DocumentChecker::slotPlaceholders()
314 QTreeWidgetItem *child = treeWidget->topLevelItem(ix);
316 if (child->data(0, statusRole).toInt() == CLIPMISSING) {
317 child->setData(0, statusRole, CLIPPLACEHOLDER);
318 child->setIcon(0, KIcon("dialog-ok"));
319 } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
320 child->setData(0, statusRole, LUMAPLACEHOLDER);
321 child->setIcon(0, KIcon("dialog-ok"));
324 child = treeWidget->topLevelItem(ix);
330 void DocumentChecker::checkStatus()
334 QTreeWidgetItem *child = treeWidget->topLevelItem(ix);
336 if (child->data(0, statusRole).toInt() == CLIPMISSING || child->data(0, statusRole).toInt() == LUMAMISSING) {
341 child = treeWidget->topLevelItem(ix);
343 buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
347 void DocumentChecker::slotDeleteSelected()
349 if (KMessageBox::warningContinueCancel(this, i18np("This will remove the selected clip from this project", "This will remove the selected clips from this project", treeWidget->selectedItems().count()), i18n("Remove clips")) == KMessageBox::Cancel) return;
351 QStringList deletedIds;
352 QTreeWidgetItem *child = treeWidget->topLevelItem(ix);
353 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
356 int id = child->data(0, statusRole).toInt();
357 if (child->isSelected() && id < 10) {
358 QString id = child->data(0, idRole).toString();
359 deletedIds.append(id);
360 for (int j = 0; j < playlists.count(); j++)
361 deletedIds.append(id + '_' + QString::number(j));
364 child = treeWidget->topLevelItem(ix);
366 kDebug() << "// Clips to delete: " << deletedIds;
368 if (!deletedIds.isEmpty()) {
370 QDomNodeList producers = m_doc.elementsByTagName("producer");
371 QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
373 QDomElement mlt = m_doc.firstChildElement("mlt");
374 QDomElement kdenlivedoc = mlt.firstChildElement("kdenlivedoc");
376 for (int i = 0; i < infoproducers.count(); i++) {
377 e = infoproducers.item(i).toElement();
378 if (deletedIds.contains(e.attribute("id"))) {
380 kdenlivedoc.removeChild(e);
385 for (int i = 0; i < producers.count(); i++) {
386 e = producers.item(i).toElement();
387 if (deletedIds.contains(e.attribute("id"))) {
394 for (int i = 0; i < playlists.count(); i++) {
395 QDomNodeList entries = playlists.at(i).toElement().elementsByTagName("entry");
396 for (int j = 0; j < playlists.count(); j++) {
397 e = entries.item(j).toElement();
398 if (deletedIds.contains(e.attribute("producer"))) {
399 // Replace clip with blank
400 e.setTagName("blank");
401 e.removeAttribute("producer");
402 int length = e.attribute("out").toInt() - e.attribute("in").toInt();
403 e.setAttribute("length", length);
411 #include "documentchecker.moc"