]> git.sesse.net Git - kdenlive/blob - src/documentchecker.cpp
Enable "recursive search" on missing lumas
[kdenlive] / src / documentchecker.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *                                                                         *
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.                                   *
8  *                                                                         *
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.                          *
13  *                                                                         *
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  ***************************************************************************/
19
20
21 #include "documentchecker.h"
22 #include "kthumb.h"
23 #include "docclipbase.h"
24 #include "titlewidget.h"
25 #include "definitions.h"
26 #include "kdenlivesettings.h"
27
28 #include <KDebug>
29 #include <KGlobalSettings>
30 #include <KFileItem>
31 #include <KIO/NetAccess>
32 #include <KFileDialog>
33 #include <KApplication>
34 #include <KUrlRequesterDialog>
35 #include <KMessageBox>
36
37 #include <QTreeWidgetItem>
38 #include <QFile>
39 #include <QHeaderView>
40 #include <QIcon>
41 #include <QPixmap>
42 #include <QTimer>
43 #include <QCryptographicHash>
44
45 const int hashRole = Qt::UserRole;
46 const int sizeRole = Qt::UserRole + 1;
47 const int idRole = Qt::UserRole + 2;
48 const int statusRole = Qt::UserRole + 3;
49 const int typeRole = Qt::UserRole + 4;
50 const int typeOriginalResource = Qt::UserRole + 5;
51
52 const int CLIPMISSING = 0;
53 const int CLIPOK = 1;
54 const int CLIPPLACEHOLDER = 2;
55 const int CLIPWRONGDURATION = 3;
56 const int PROXYMISSING = 4;
57
58 const int LUMAMISSING = 10;
59 const int LUMAOK = 11;
60 const int LUMAPLACEHOLDER = 12;
61
62 enum TITLECLIPTYPE { TITLE_IMAGE_ELEMENT = 20, TITLE_FONT_ELEMENT = 21 };
63
64 DocumentChecker::DocumentChecker(QDomNodeList infoproducers, QDomDocument doc):
65     m_info(infoproducers), m_doc(doc), m_dialog(NULL)
66 {
67
68 }
69
70
71 bool DocumentChecker::hasErrorInClips()
72 {
73     int clipType;
74     QDomElement e;
75     QString resource;
76     QDomNodeList documentProducers = m_doc.elementsByTagName("producer");
77     QList <QDomElement> wrongDurationClips;
78     QList <QDomElement> missingClips;
79     QList <QDomElement> missingProxies;
80     for (int i = 0; i < m_info.count(); i++) {
81         e = m_info.item(i).toElement();
82         clipType = e.attribute("type").toInt();
83         if (clipType == COLOR) continue;
84         if (clipType != TEXT && clipType != IMAGE) {
85             QString id = e.attribute("id");
86             int duration = e.attribute("duration").toInt();
87             // Check that the duration is in sync between Kdenlive's info and MLT's playlist
88             for (int j = 0; j < documentProducers.count(); j++) {
89                 QDomElement mltProd = documentProducers.at(j).toElement();
90                 QString prodId = mltProd.attribute("id");
91                 // Don't check slowmotion clips for now... (TODO?)
92                 if (prodId.startsWith("slowmotion")) continue;
93                 if (prodId.contains("_")) prodId = prodId.section("_", 0, 0);
94                 if (prodId != id) continue;
95                 int mltDuration = mltProd.attribute("out").toInt() + 1;
96                 if (mltDuration != duration && !e.hasAttribute("_mismatch")) {
97                     // Duration mismatch
98                     e.setAttribute("_mismatch", mltDuration);
99                     if (!wrongDurationClips.contains(e)) wrongDurationClips.append(e);
100                     kDebug() << "WRONG DURTAION: "<<id<<", TYPE: "<<clipType;
101                 }
102             }
103         }
104         
105         if (clipType == TEXT) {
106             //TODO: Check is clip template is missing (xmltemplate) or hash changed
107             QStringList images = TitleWidget::extractImageList(e.attribute("xmldata"));
108             QStringList fonts = TitleWidget::extractFontList(e.attribute("xmldata"));
109             checkMissingImages(missingClips, images, fonts, e.attribute("id"), e.attribute("name"));
110             continue;
111         }
112         resource = e.attribute("resource");
113         if (e.hasAttribute("proxy")) {
114             QString proxyresource = e.attribute("proxy");
115             if (!proxyresource.isEmpty() && proxyresource != "-" && !KIO::NetAccess::exists(KUrl(proxyresource), KIO::NetAccess::SourceSide, 0)) {
116                 // Missing clip found
117                 missingProxies.append(e);
118             }
119         }
120         if (clipType == SLIDESHOW) resource = KUrl(resource).directory();
121         if (!KIO::NetAccess::exists(KUrl(resource), KIO::NetAccess::SourceSide, 0)) {
122             // Missing clip found
123             missingClips.append(e);
124         } else {
125             // Check if the clip has changed
126             if (clipType != SLIDESHOW && e.hasAttribute("file_hash")) {
127                 if (e.attribute("file_hash") != DocClipBase::getHash(e.attribute("resource")))
128                     e.removeAttribute("file_hash");
129             }
130         }
131     }
132
133     QStringList missingLumas;
134     QString root = m_doc.documentElement().attribute("root");
135     if (!root.isEmpty()) root = KUrl(root).path(KUrl::AddTrailingSlash);
136     QDomNodeList trans = m_doc.elementsByTagName("transition");
137     for (int i = 0; i < trans.count(); i++) {
138         QString luma = getProperty(trans.at(i).toElement(), "luma");
139         if (!luma.isEmpty()) {
140             QString lumaPath = luma;
141             if (!lumaPath.startsWith('/')) lumaPath.prepend(root);
142             if (!QFile::exists(lumaPath) && !missingLumas.contains(luma)) {
143                 missingLumas.append(luma);
144             }
145         }
146     }
147
148     if (missingClips.isEmpty() && missingLumas.isEmpty() && wrongDurationClips.isEmpty() && missingProxies.isEmpty())
149         return false;
150
151     m_dialog = new QDialog();
152     m_dialog->setFont(KGlobalSettings::toolBarFont());
153     m_ui.setupUi(m_dialog);
154
155     foreach(const QString l, missingLumas) {
156         QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Luma file") << l);
157         item->setIcon(0, KIcon("dialog-close"));
158         item->setData(0, idRole, l);
159         item->setData(0, statusRole, LUMAMISSING);
160     }
161
162     m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
163     for (int i = 0; i < missingClips.count(); i++) {
164         e = missingClips.at(i).toElement();
165         QString clipType;
166         int t = e.attribute("type").toInt();
167         switch (t) {
168         case AV:
169             clipType = i18n("Video clip");
170             break;
171         case VIDEO:
172             clipType = i18n("Mute video clip");
173             break;
174         case AUDIO:
175             clipType = i18n("Audio clip");
176             break;
177         case PLAYLIST:
178             clipType = i18n("Playlist clip");
179             break;
180         case IMAGE:
181             clipType = i18n("Image clip");
182             break;
183         case SLIDESHOW:
184             clipType = i18n("Slideshow clip");
185             break;
186         case TITLE_IMAGE_ELEMENT:
187             clipType = i18n("Title Image");
188             break;
189         case TITLE_FONT_ELEMENT:
190             clipType = i18n("Title Font");
191             break;
192         default:
193             clipType = i18n("Video clip");
194         }
195         QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType);
196         if (t == TITLE_IMAGE_ELEMENT) {
197             item->setIcon(0, KIcon("dialog-warning"));
198             item->setToolTip(1, e.attribute("name"));
199             item->setText(1, e.attribute("resource"));
200             item->setData(0, statusRole, CLIPPLACEHOLDER);
201             item->setData(0, typeOriginalResource, e.attribute("resource"));
202         } else if (t == TITLE_FONT_ELEMENT) {
203             item->setIcon(0, KIcon("dialog-warning"));
204             item->setToolTip(1, e.attribute("name"));
205             QString ft = e.attribute("resource");
206             QString newft = QFontInfo(QFont(ft)).family();
207             item->setText(1, i18n("%1 will be replaced by %2", ft, newft));
208             item->setData(0, statusRole, CLIPPLACEHOLDER);
209         } else {
210             item->setIcon(0, KIcon("dialog-close"));
211             item->setText(1, e.attribute("resource"));
212             item->setData(0, hashRole, e.attribute("file_hash"));
213             item->setData(0, sizeRole, e.attribute("file_size"));
214             item->setData(0, statusRole, CLIPMISSING);
215         }
216         item->setData(0, typeRole, t);
217         item->setData(0, idRole, e.attribute("id"));
218         item->setToolTip(0, i18n("Missing item"));
219     }
220
221     if (missingClips.count() > 0) {
222         if (wrongDurationClips.count() > 0) {
223             m_ui.infoLabel->setText(i18n("The project file contains missing clips or files and clip duration mismatch"));
224         }
225         else {
226             m_ui.infoLabel->setText(i18n("The project file contains missing clips or files"));
227         }
228     }
229     else if (wrongDurationClips.count() > 0) {
230         m_ui.infoLabel->setText(i18n("The project file contains clips with duration mismatch"));
231     }
232     if (missingProxies.count() > 0) {
233         if (!m_ui.infoLabel->text().isEmpty()) m_ui.infoLabel->setText(m_ui.infoLabel->text() + ". ");
234         m_ui.infoLabel->setText(m_ui.infoLabel->text() + i18n("Missing proxies will be recreated after opening."));
235     }
236
237     m_ui.removeSelected->setEnabled(!missingClips.isEmpty());
238     m_ui.recursiveSearch->setEnabled(!missingClips.isEmpty() || !missingLumas.isEmpty());
239     m_ui.usePlaceholders->setEnabled(!missingClips.isEmpty());
240     m_ui.fixDuration->setEnabled(!wrongDurationClips.isEmpty());
241         
242     for (int i = 0; i < wrongDurationClips.count(); i++) {
243         e = wrongDurationClips.at(i).toElement();
244         QString clipType;
245         int t = e.attribute("type").toInt();
246         switch (t) {
247         case AV:
248             clipType = i18n("Video clip");
249             break;
250         case VIDEO:
251             clipType = i18n("Mute video clip");
252             break;
253         case AUDIO:
254             clipType = i18n("Audio clip");
255             break;
256         case PLAYLIST:
257             clipType = i18n("Playlist clip");
258             break;
259         case IMAGE:
260             clipType = i18n("Image clip");
261             break;
262         case SLIDESHOW:
263             clipType = i18n("Slideshow clip");
264             break;
265         default:
266             clipType = i18n("Video clip");
267         }
268         QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType);
269         item->setIcon(0, KIcon("timeadjust"));
270         item->setText(1, e.attribute("resource"));
271         item->setData(0, hashRole, e.attribute("file_hash"));
272         item->setData(0, sizeRole, e.attribute("_mismatch"));
273         e.removeAttribute("_mismatch");
274         item->setData(0, statusRole, CLIPWRONGDURATION);
275         item->setData(0, typeRole, t);
276         item->setData(0, idRole, e.attribute("id"));
277         item->setToolTip(0, i18n("Duration mismatch"));
278     }
279
280     if (missingProxies.count() > 0) {
281         QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Proxy clip"));
282         item->setIcon(0, KIcon("dialog-warning"));
283         item->setText(1, i18n("%1 missing proxy clips, will be recreated on project opening", missingProxies.count()));
284         item->setData(0, hashRole, e.attribute("file_hash"));
285         item->setData(0, statusRole, PROXYMISSING);
286         item->setToolTip(0, i18n("Missing proxy"));
287     }
288
289     for (int i = 0; i < missingProxies.count(); i++) {
290         e = missingProxies.at(i).toElement();
291         QString clipType;
292         QString realPath = e.attribute("resource");
293         QString id = e.attribute("id");
294         // Replace proxy url with real clip in MLT producers
295         QDomNodeList properties;
296         QDomElement mltProd;
297         QDomElement property;
298         for (int j = 0; j < documentProducers.count(); j++) {
299             mltProd = documentProducers.at(j).toElement();
300             QString prodId = mltProd.attribute("id");
301             bool slowmotion = false;
302             if (prodId.startsWith("slowmotion")) {
303                 slowmotion = true;
304                 prodId = prodId.section(':', 1, 1);
305             }
306             if (prodId.contains('_')) prodId = prodId.section('_', 0, 0);
307             if (prodId == id) {
308                 // Hit, we must replace url
309                 properties = mltProd.childNodes();
310                 for (int k = 0; k < properties.count(); ++k) {
311                     property = properties.item(k).toElement();
312                     if (property.attribute("name") == "resource") {
313                         QString resource = property.firstChild().nodeValue();                    
314                         QString suffix;
315                         if (slowmotion) suffix = "?" + resource.section('?', -1);
316                         property.firstChild().setNodeValue(realPath + suffix);
317                         break;
318                     }
319                 }
320             }
321         }
322     }
323     
324     if (missingProxies.count() > 0) {
325         // original doc was modified
326         QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
327         QDomElement infoXml = infoXmlNode.toElement();
328         infoXml.setAttribute("modified", "1");
329     }
330     
331     connect(m_ui.recursiveSearch, SIGNAL(pressed()), this, SLOT(slotSearchClips()));
332     connect(m_ui.usePlaceholders, SIGNAL(pressed()), this, SLOT(slotPlaceholders()));
333     connect(m_ui.removeSelected, SIGNAL(pressed()), this, SLOT(slotDeleteSelected()));
334     connect(m_ui.fixDuration, SIGNAL(pressed()), this, SLOT(slotFixDuration()));
335     connect(m_ui.treeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotEditItem(QTreeWidgetItem *, int)));
336     connect(m_ui.treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckButtons()));
337     //adjustSize();
338     if (m_ui.treeWidget->topLevelItem(0)) m_ui.treeWidget->setCurrentItem(m_ui.treeWidget->topLevelItem(0));
339     checkStatus();
340     int acceptMissing = m_dialog->exec();
341     if (acceptMissing == QDialog::Accepted) acceptDialog();
342     return (acceptMissing != QDialog::Accepted);
343 }
344
345 DocumentChecker::~DocumentChecker()
346 {
347     if (m_dialog) delete m_dialog;
348 }
349
350
351 QString DocumentChecker::getProperty(QDomElement effect, const QString &name)
352 {
353     QDomNodeList params = effect.elementsByTagName("property");
354     for (int i = 0; i < params.count(); i++) {
355         QDomElement e = params.item(i).toElement();
356         if (e.attribute("name") == name) {
357             return e.firstChild().nodeValue();
358         }
359     }
360     return QString();
361 }
362
363 void DocumentChecker::setProperty(QDomElement effect, const QString &name, const QString value)
364 {
365     QDomNodeList params = effect.elementsByTagName("property");
366     for (int i = 0; i < params.count(); i++) {
367         QDomElement e = params.item(i).toElement();
368         if (e.attribute("name") == name) {
369             e.firstChild().setNodeValue(value);
370         }
371     }
372 }
373
374 void DocumentChecker::slotSearchClips()
375 {
376     QString newpath = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow(), i18n("Clips folder"));
377     if (newpath.isEmpty()) return;
378     int ix = 0;
379     bool fixed = false;
380     m_ui.recursiveSearch->setEnabled(false);
381     QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
382     while (child) {
383         if (child->data(0, statusRole).toInt() == CLIPMISSING) {
384             QString clipPath = searchFileRecursively(QDir(newpath), child->data(0, sizeRole).toString(), child->data(0, hashRole).toString());
385             if (!clipPath.isEmpty()) {
386                 fixed = true;
387                 child->setText(1, clipPath);
388                 child->setIcon(0, KIcon("dialog-ok"));
389                 child->setData(0, statusRole, CLIPOK);
390             }
391         } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
392             QString fileName = searchLuma(child->data(0, idRole).toString());
393             if (!fileName.isEmpty()) {
394                 fixed = true;
395                 child->setText(1, fileName);
396                 child->setIcon(0, KIcon("dialog-ok"));
397                 child->setData(0, statusRole, LUMAOK);
398             }
399         }
400         ix++;
401         child = m_ui.treeWidget->topLevelItem(ix);
402     }
403     m_ui.recursiveSearch->setEnabled(true);
404     if (fixed) {
405         // original doc was modified
406         QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
407         QDomElement infoXml = infoXmlNode.toElement();
408         infoXml.setAttribute("modified", "1");
409     }
410     checkStatus();
411 }
412
413
414 QString DocumentChecker::searchLuma(QString file) const
415 {
416     KUrl searchPath(KdenliveSettings::mltpath());
417     if (file.contains("PAL"))
418         searchPath.cd("../lumas/PAL");
419     else
420         searchPath.cd("../lumas/NTSC");
421     QString result = searchPath.path(KUrl::AddTrailingSlash) + KUrl(file).fileName();
422     if (QFile::exists(result))
423         return result;
424     // try to find luma in application path
425     searchPath.clear();
426     searchPath = KUrl(QCoreApplication::applicationDirPath());
427     searchPath.cd("../share/apps/kdenlive/lumas");
428     result = searchPath.path(KUrl::AddTrailingSlash) + KUrl(file).fileName();
429     if (QFile::exists(result))
430         return result;
431     return QString();
432 }
433
434
435 QString DocumentChecker::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const
436 {
437     QString foundFileName;
438     QByteArray fileData;
439     QByteArray fileHash;
440     QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable);
441     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
442         QFile file(dir.absoluteFilePath(filesAndDirs.at(i)));
443         if (QString::number(file.size()) == matchSize) {
444             if (file.open(QIODevice::ReadOnly)) {
445                 /*
446                 * 1 MB = 1 second per 450 files (or faster)
447                 * 10 MB = 9 seconds per 450 files (or faster)
448                 */
449                 if (file.size() > 1000000 * 2) {
450                     fileData = file.read(1000000);
451                     if (file.seek(file.size() - 1000000))
452                         fileData.append(file.readAll());
453                 } else
454                     fileData = file.readAll();
455                 file.close();
456                 fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
457                 if (QString(fileHash.toHex()) == matchHash)
458                     return file.fileName();
459             }
460         }
461         //kDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex();
462     }
463     filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
464     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
465         foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash);
466         if (!foundFileName.isEmpty())
467             break;
468     }
469     return foundFileName;
470 }
471
472 void DocumentChecker::slotEditItem(QTreeWidgetItem *item, int)
473 {
474     int t = item->data(0, typeRole).toInt();
475     if (t == TITLE_FONT_ELEMENT) return;
476     //|| t == TITLE_IMAGE_ELEMENT) {
477
478     KUrl url = KUrlRequesterDialog::getUrl(item->text(1), m_dialog, i18n("Enter new location for file"));
479     if (url.isEmpty()) return;
480     item->setText(1, url.path());
481     if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) {
482         item->setIcon(0, KIcon("dialog-ok"));
483         int id = item->data(0, statusRole).toInt();
484         if (id < 10) item->setData(0, statusRole, CLIPOK);
485         else item->setData(0, statusRole, LUMAOK);
486         checkStatus();
487     } else {
488         item->setIcon(0, KIcon("dialog-close"));
489         int id = item->data(0, statusRole).toInt();
490         if (id < 10) item->setData(0, statusRole, CLIPMISSING);
491         else item->setData(0, statusRole, LUMAMISSING);
492         checkStatus();
493     }
494 }
495
496
497 void DocumentChecker::acceptDialog()
498 {
499     QDomElement e, property;
500     QDomNodeList producers = m_doc.elementsByTagName("producer");
501     QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
502     QDomNodeList properties;
503     int ix = 0;
504
505     // prepare transitions
506     QDomNodeList trans = m_doc.elementsByTagName("transition");
507
508     // Mark document as modified
509     m_doc.documentElement().setAttribute("modified", 1);
510
511     QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
512     while (child) {
513         int t = child->data(0, typeRole).toInt();
514         if (child->data(0, statusRole).toInt() == CLIPOK) {
515             QString id = child->data(0, idRole).toString();
516             if (t == TITLE_IMAGE_ELEMENT) {
517                 // edit images embedded in titles
518                 for (int i = 0; i < infoproducers.count(); i++) {
519                     e = infoproducers.item(i).toElement();
520                     if (e.attribute("id") == id) {
521                         // Fix clip
522                         QString xml = e.attribute("xmldata");
523                         xml.replace(child->data(0, typeOriginalResource).toString(), child->text(1));
524                         e.setAttribute("xmldata", xml);
525                         break;
526                     }
527                 }
528                 for (int i = 0; i < producers.count(); i++) {
529                     e = producers.item(i).toElement();
530                     if (e.attribute("id").section('_', 0, 0) == id) {
531                         // Fix clip
532                         properties = e.childNodes();
533                         for (int j = 0; j < properties.count(); ++j) {
534                             property = properties.item(j).toElement();
535                             if (property.attribute("name") == "xmldata") {
536                                 QString xml = property.firstChild().nodeValue();
537                                 xml.replace(child->data(0, typeOriginalResource).toString(), child->text(1));
538                                 property.firstChild().setNodeValue(xml);
539                                 break;
540                             }
541                         }
542                     }
543                 }
544             } else {
545                 // edit clip url
546                 for (int i = 0; i < infoproducers.count(); i++) {
547                     e = infoproducers.item(i).toElement();
548                     if (e.attribute("id") == id) {
549                         // Fix clip
550                         e.setAttribute("resource", child->text(1));
551                         e.setAttribute("name", KUrl(child->text(1)).fileName());
552                         break;
553                     }
554                 }
555                 for (int i = 0; i < producers.count(); i++) {
556                     e = producers.item(i).toElement();
557                     if (e.attribute("id").section('_', 0, 0) == id || e.attribute("id").section(':', 1, 1) == id) {
558                         // Fix clip
559                         properties = e.childNodes();
560                         for (int j = 0; j < properties.count(); ++j) {
561                             property = properties.item(j).toElement();
562                             if (property.attribute("name") == "resource") {
563                                 QString resource = property.firstChild().nodeValue();
564                                 if (resource.contains(QRegExp("\\?[0-9]+\\.[0-9]+(&amp;strobe=[0-9]+)?$")))
565                                     property.firstChild().setNodeValue(child->text(1) + '?' + resource.section('?', -1));
566                                 else
567                                     property.firstChild().setNodeValue(child->text(1));
568                                 break;
569                             }
570                         }
571                     }
572                 }
573             }
574         } else if (child->data(0, statusRole).toInt() == CLIPPLACEHOLDER && t != TITLE_FONT_ELEMENT && t != TITLE_IMAGE_ELEMENT) {
575             QString id = child->data(0, idRole).toString();
576             for (int i = 0; i < infoproducers.count(); i++) {
577                 e = infoproducers.item(i).toElement();
578                 if (e.attribute("id") == id) {
579                     // Fix clip
580                     e.setAttribute("placeholder", '1');
581                     break;
582                 }
583             }
584         } else if (child->data(0, statusRole).toInt() == LUMAOK) {
585             for (int i = 0; i < trans.count(); i++) {
586                 QString luma = getProperty(trans.at(i).toElement(), "luma");
587                 
588                 kDebug() << "luma: " << luma;
589                 if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) {
590                     setProperty(trans.at(i).toElement(), "luma", child->text(1));
591                     kDebug() << "replace with; " << child->text(1);
592                 }
593             }
594         } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
595             for (int i = 0; i < trans.count(); i++) {
596                 QString luma = getProperty(trans.at(i).toElement(), "luma");
597                 if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) {
598                     setProperty(trans.at(i).toElement(), "luma", QString());
599                 }
600             }
601         }
602         ix++;
603         child = m_ui.treeWidget->topLevelItem(ix);
604     }
605     //QDialog::accept();
606 }
607
608 void DocumentChecker::slotPlaceholders()
609 {
610     int ix = 0;
611     QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
612     while (child) {
613         if (child->data(0, statusRole).toInt() == CLIPMISSING) {
614             child->setData(0, statusRole, CLIPPLACEHOLDER);
615             child->setIcon(0, KIcon("dialog-ok"));
616         } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
617             child->setData(0, statusRole, LUMAPLACEHOLDER);
618             child->setIcon(0, KIcon("dialog-ok"));
619         }
620         ix++;
621         child = m_ui.treeWidget->topLevelItem(ix);
622     }
623     checkStatus();
624 }
625
626 void DocumentChecker::slotFixDuration()
627 {
628     int ix = 0;
629     QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
630     while (child) {
631         if (child->data(0, statusRole).toInt() == CLIPWRONGDURATION) {
632             child->setData(0, statusRole, CLIPOK);
633             child->setIcon(0, KIcon("dialog-ok"));
634             QString id = child->data(0, idRole).toString();
635             for (int i = 0; i < m_info.count(); i++) {
636                 QDomElement e = m_info.at(i).toElement();
637                 if (e.attribute("id") == id) {
638                     e.setAttribute("duration", child->data(0, sizeRole).toString());
639                     break;
640                 }
641             }
642         }
643         ix++;
644         child = m_ui.treeWidget->topLevelItem(ix);
645     }
646     QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
647     QDomElement infoXml = infoXmlNode.toElement();
648     infoXml.setAttribute("modified", "1");
649     m_ui.fixDuration->setEnabled(false);
650     checkStatus();
651 }
652
653
654 void DocumentChecker::checkStatus()
655 {
656     bool status = true;
657     int ix = 0;
658     QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
659     while (child) {
660         int status = child->data(0, statusRole).toInt();
661         if (status == CLIPMISSING || status == LUMAMISSING || status == CLIPWRONGDURATION) {
662             status = false;
663             break;
664         }
665         ix++;
666         child = m_ui.treeWidget->topLevelItem(ix);
667     }
668     m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
669 }
670
671
672 void DocumentChecker::slotDeleteSelected()
673 {
674     if (KMessageBox::warningContinueCancel(m_dialog, i18np("This will remove the selected clip from this project", "This will remove the selected clips from this project", m_ui.treeWidget->selectedItems().count()), i18n("Remove clips")) == KMessageBox::Cancel)
675         return;
676     QStringList deletedIds;
677     QStringList deletedLumas;
678     QDomNodeList playlists = m_doc.elementsByTagName("playlist");
679
680     foreach(QTreeWidgetItem *child, m_ui.treeWidget->selectedItems()) {
681         int id = child->data(0, statusRole).toInt();
682         if (id == CLIPMISSING) {
683             deletedIds.append(child->data(0, idRole).toString());
684             delete child;
685         }
686         else if (id == LUMAMISSING) {
687             deletedLumas.append(child->data(0, idRole).toString());
688             delete child;
689         }
690     }
691
692     if (!deletedLumas.isEmpty()) {
693         QDomElement e;
694         QDomNodeList transitions = m_doc.elementsByTagName("transition");
695         foreach (QString lumaPath, deletedLumas) {
696             for (int i = 0; i < transitions.count(); i++) {
697                 e = transitions.item(i).toElement();
698                 QString resource = EffectsList::property(e, "luma");
699                 if (resource == lumaPath) EffectsList::removeProperty(e, "luma");
700             }
701         }
702     }
703
704     if (!deletedIds.isEmpty()) {
705         QDomElement e;
706         QDomNodeList producers = m_doc.elementsByTagName("producer");
707         QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
708
709         QDomElement mlt = m_doc.firstChildElement("mlt");
710         QDomElement kdenlivedoc = mlt.firstChildElement("kdenlivedoc");
711
712         for (int i = 0, j = 0; i < infoproducers.count() && j < deletedIds.count(); i++) {
713             e = infoproducers.item(i).toElement();
714             if (deletedIds.contains(e.attribute("id"))) {
715                 // Remove clip
716                 kdenlivedoc.removeChild(e);
717                 i--;
718                 j++;
719             }
720         }
721
722         for (int i = 0; i < producers.count(); i++) {
723             e = producers.item(i).toElement();
724             if (deletedIds.contains(e.attribute("id").section('_', 0, 0)) || deletedIds.contains(e.attribute("id").section(':', 1, 1).section('_', 0, 0))) {
725                 // Remove clip
726                 mlt.removeChild(e);
727                 i--;
728             }
729         }
730
731         for (int i = 0; i < playlists.count(); i++) {
732             QDomNodeList entries = playlists.at(i).toElement().elementsByTagName("entry");
733             for (int j = 0; j < entries.count(); j++) {
734                 e = entries.item(j).toElement();
735                 if (deletedIds.contains(e.attribute("producer").section('_', 0, 0)) || deletedIds.contains(e.attribute("producer").section(':', 1, 1).section('_', 0, 0))) {
736                     // Replace clip with blank
737                     while (e.childNodes().count() > 0)
738                         e.removeChild(e.firstChild());
739                     e.setTagName("blank");
740                     e.removeAttribute("producer");
741                     int length = e.attribute("out").toInt() - e.attribute("in").toInt();
742                     e.setAttribute("length", length);
743                     j--;
744                 }
745             }
746         }
747         QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
748         QDomElement infoXml = infoXmlNode.toElement();
749         infoXml.setAttribute("modified", "1");
750         checkStatus();
751     }
752 }
753
754 void DocumentChecker::checkMissingImages(QList <QDomElement>&missingClips, QStringList images, QStringList fonts, QString id, QString baseClip)
755 {
756     QDomDocument doc;
757     foreach(const QString &img, images) {
758         if (!KIO::NetAccess::exists(KUrl(img), KIO::NetAccess::SourceSide, 0)) {
759             QDomElement e = doc.createElement("missingclip");
760             e.setAttribute("type", TITLE_IMAGE_ELEMENT);
761             e.setAttribute("resource", img);
762             e.setAttribute("id", id);
763             e.setAttribute("name", baseClip);
764             missingClips.append(e);
765         }
766     }
767     kDebug() << "/ / / CHK FONTS: " << fonts;
768     foreach(const QString &fontelement, fonts) {
769         QFont f(fontelement);
770         kDebug() << "/ / / CHK FONTS: " << fontelement << " = " << QFontInfo(f).family();
771         if (fontelement != QFontInfo(f).family()) {
772             QDomElement e = doc.createElement("missingclip");
773             e.setAttribute("type", TITLE_FONT_ELEMENT);
774             e.setAttribute("resource", fontelement);
775             e.setAttribute("id", id);
776             e.setAttribute("name", baseClip);
777             missingClips.append(e);
778         }
779     }
780 }
781
782
783 void DocumentChecker::slotCheckButtons()
784 {
785     if (m_ui.treeWidget->currentItem()) {
786         QTreeWidgetItem *item = m_ui.treeWidget->currentItem();
787         int t = item->data(0, typeRole).toInt();
788         int s = item->data(0, statusRole).toInt();
789         if (t == TITLE_FONT_ELEMENT || t == TITLE_IMAGE_ELEMENT || s == PROXYMISSING) {
790             m_ui.removeSelected->setEnabled(false);
791         } else m_ui.removeSelected->setEnabled(true);
792     }
793
794 }
795
796 #include "documentchecker.moc"
797
798