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 "docclipbase.h"
24 #include "titlewidget.h"
25 #include "definitions.h"
26 #include "kdenlivesettings.h"
29 #include <KGlobalSettings>
31 #include <KIO/NetAccess>
32 #include <KFileDialog>
33 #include <KApplication>
34 #include <KUrlRequesterDialog>
35 #include <KMessageBox>
36 #include <KStandardDirs>
38 #include <QTreeWidgetItem>
40 #include <QHeaderView>
44 #include <QCryptographicHash>
46 const int hashRole = Qt::UserRole;
47 const int sizeRole = Qt::UserRole + 1;
48 const int idRole = Qt::UserRole + 2;
49 const int statusRole = Qt::UserRole + 3;
50 const int typeRole = Qt::UserRole + 4;
51 const int typeOriginalResource = Qt::UserRole + 5;
52 const int resetDurationRole = Qt::UserRole + 6;
54 const int CLIPMISSING = 0;
56 const int CLIPPLACEHOLDER = 2;
57 const int CLIPWRONGDURATION = 3;
58 const int PROXYMISSING = 4;
59 const int SOURCEMISSING = 5;
61 const int LUMAMISSING = 10;
62 const int LUMAOK = 11;
63 const int LUMAPLACEHOLDER = 12;
65 enum TITLECLIPTYPE { TITLE_IMAGE_ELEMENT = 20, TITLE_FONT_ELEMENT = 21 };
67 DocumentChecker::DocumentChecker(QDomNodeList infoproducers, QDomDocument doc):
68 m_info(infoproducers), m_doc(doc), m_dialog(NULL)
74 bool DocumentChecker::hasErrorInClips()
80 QDomNodeList documentProducers = m_doc.elementsByTagName("producer");
81 QList <QDomElement> wrongDurationClips;
82 // List clips whose proxy is missing
83 QList <QDomElement> missingProxies;
84 // List clips who have a working proxy but no source clip
85 QList <QDomElement> missingSources;
89 for (int i = 0; i < max; i++) {
90 e = m_info.item(i).toElement();
91 clipType = e.attribute("type").toInt();
92 if (clipType == COLOR) continue;
93 if (clipType != TEXT && clipType != IMAGE && clipType != SLIDESHOW) {
94 QString id = e.attribute("id");
95 int duration = e.attribute("duration").toInt();
99 // Check that the duration is in sync between Kdenlive's info and MLT's playlist
100 int prodsCount = documentProducers.count();
101 for (int j = 0; j < prodsCount; j++) {
102 mltProd = documentProducers.at(j).toElement();
103 prodId = mltProd.attribute("id");
104 // Don't check slowmotion clips for now... (TODO?)
105 if (prodId.startsWith("slowmotion")) continue;
106 if (prodId.contains("_")) prodId = prodId.section("_", 0, 0);
107 if (prodId != id) continue;
108 if (mltDuration > 0 ) {
109 // We have several MLT producers for the same clip (probably track producers)
110 int newLength = EffectsList::property(mltProd, "length").toInt();
111 if (newLength != mltDuration) {
112 // we have a different duration for the same clip, that is not safe
113 e.setAttribute("_resetDuration", 1);
116 mltDuration = EffectsList::property(mltProd, "length").toInt();
117 if (mltDuration != duration) {
119 e.setAttribute("_mismatch", mltDuration);
120 if (mltDuration == 15000) {
121 // a length of 15000 might indicate a wrong clip length since it is a default length
122 e.setAttribute("_resetDuration", 1);
124 if (!wrongDurationClips.contains(e)) wrongDurationClips.append(e);
129 if (clipType == TEXT) {
130 //TODO: Check is clip template is missing (xmltemplate) or hash changed
131 QStringList images = TitleWidget::extractImageList(e.attribute("xmldata"));
132 QStringList fonts = TitleWidget::extractFontList(e.attribute("xmldata"));
133 checkMissingImagesAndFonts(images, fonts, e.attribute("id"), e.attribute("name"));
136 resource = e.attribute("resource");
137 if (e.hasAttribute("proxy")) {
138 QString proxyresource = e.attribute("proxy");
139 if (!proxyresource.isEmpty() && proxyresource != "-") {
141 if (!KIO::NetAccess::exists(KUrl(proxyresource), KIO::NetAccess::SourceSide, 0)) {
142 // Missing clip found
143 missingProxies.append(e);
145 else if (!KIO::NetAccess::exists(KUrl(resource), KIO::NetAccess::SourceSide, 0)) {
146 // clip has proxy but original clip is missing
147 missingSources.append(e);
152 if (clipType == SLIDESHOW) resource = KUrl(resource).directory();
153 if (!KIO::NetAccess::exists(KUrl(resource), KIO::NetAccess::SourceSide, 0)) {
154 // Missing clip found
155 m_missingClips.append(e);
157 // Check if the clip has changed
158 if (clipType != SLIDESHOW && e.hasAttribute("file_hash")) {
159 if (e.attribute("file_hash") != DocClipBase::getHash(e.attribute("resource")))
160 e.removeAttribute("file_hash");
165 // Get list of used Luma files
166 QStringList missingLumas;
167 QStringList filesToCheck;
169 QString root = m_doc.documentElement().attribute("root");
170 if (!root.isEmpty()) root = KUrl(root).path(KUrl::AddTrailingSlash);
171 QDomNodeList trans = m_doc.elementsByTagName("transition");
173 for (int i = 0; i < max; i++) {
174 QString luma = getProperty(trans.at(i).toElement(), "luma");
175 if (!luma.isEmpty() && !filesToCheck.contains(luma))
176 filesToCheck.append(luma);
178 // Check existence of luma files
179 foreach (const QString lumafile, filesToCheck) {
181 if (!filePath.startsWith('/')) filePath.prepend(root);
182 if (!QFile::exists(filePath)) {
183 missingLumas.append(lumafile);
189 if (m_missingClips.isEmpty() && missingLumas.isEmpty() && wrongDurationClips.isEmpty() && missingProxies.isEmpty() && missingSources.isEmpty())
192 m_dialog = new QDialog();
193 m_dialog->setFont(KGlobalSettings::toolBarFont());
194 m_ui.setupUi(m_dialog);
196 foreach(const QString l, missingLumas) {
197 QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Luma file") << l);
198 item->setIcon(0, KIcon("dialog-close"));
199 item->setData(0, idRole, l);
200 item->setData(0, statusRole, LUMAMISSING);
203 m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
204 max = m_missingClips.count();
205 for (int i = 0; i < max; i++) {
206 e = m_missingClips.at(i).toElement();
208 int t = e.attribute("type").toInt();
211 clipType = i18n("Video clip");
214 clipType = i18n("Mute video clip");
217 clipType = i18n("Audio clip");
220 clipType = i18n("Playlist clip");
223 clipType = i18n("Image clip");
226 clipType = i18n("Slideshow clip");
228 case TITLE_IMAGE_ELEMENT:
229 clipType = i18n("Title Image");
231 case TITLE_FONT_ELEMENT:
232 clipType = i18n("Title Font");
235 clipType = i18n("Video clip");
237 QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType);
238 if (t == TITLE_IMAGE_ELEMENT) {
239 item->setIcon(0, KIcon("dialog-warning"));
240 item->setToolTip(1, e.attribute("name"));
241 item->setText(1, e.attribute("resource"));
242 item->setData(0, statusRole, CLIPPLACEHOLDER);
243 item->setData(0, typeOriginalResource, e.attribute("resource"));
244 } else if (t == TITLE_FONT_ELEMENT) {
245 item->setIcon(0, KIcon("dialog-warning"));
246 item->setToolTip(1, e.attribute("name"));
247 QString ft = e.attribute("resource");
248 QString newft = QFontInfo(QFont(ft)).family();
249 item->setText(1, i18n("%1 will be replaced by %2", ft, newft));
250 item->setData(0, statusRole, CLIPPLACEHOLDER);
252 item->setIcon(0, KIcon("dialog-close"));
253 item->setText(1, e.attribute("resource"));
254 item->setData(0, hashRole, e.attribute("file_hash"));
255 item->setData(0, sizeRole, e.attribute("file_size"));
256 item->setData(0, statusRole, CLIPMISSING);
258 item->setData(0, typeRole, t);
259 item->setData(0, idRole, e.attribute("id"));
260 item->setToolTip(0, i18n("Missing item"));
263 if (m_missingClips.count() > 0) {
264 if (wrongDurationClips.count() > 0) {
265 m_ui.infoLabel->setText(i18n("The project file contains missing clips or files and clip duration mismatch"));
268 m_ui.infoLabel->setText(i18n("The project file contains missing clips or files"));
271 else if (wrongDurationClips.count() > 0) {
272 m_ui.infoLabel->setText(i18n("The project file contains clips with duration mismatch"));
274 if (missingProxies.count() > 0) {
275 if (!m_ui.infoLabel->text().isEmpty()) m_ui.infoLabel->setText(m_ui.infoLabel->text() + ". ");
276 m_ui.infoLabel->setText(m_ui.infoLabel->text() + i18n("Missing proxies will be recreated after opening."));
278 if (missingSources.count() > 0) {
279 if (!m_ui.infoLabel->text().isEmpty()) m_ui.infoLabel->setText(m_ui.infoLabel->text() + ". ");
280 m_ui.infoLabel->setText(m_ui.infoLabel->text() + i18np("The project file contains a missing clip, you can still work with its proxy.", "The project file contains missing clips, you can still work with their proxies.", missingSources.count()));
283 m_ui.removeSelected->setEnabled(!m_missingClips.isEmpty());
284 m_ui.recursiveSearch->setEnabled(!m_missingClips.isEmpty() || !missingLumas.isEmpty());
285 m_ui.usePlaceholders->setEnabled(!m_missingClips.isEmpty());
286 m_ui.fixDuration->setEnabled(!wrongDurationClips.isEmpty());
288 max = wrongDurationClips.count();
289 for (int i = 0; i < max; i++) {
290 e = wrongDurationClips.at(i).toElement();
292 int t = e.attribute("type").toInt();
295 clipType = i18n("Video clip");
298 clipType = i18n("Mute video clip");
301 clipType = i18n("Audio clip");
304 clipType = i18n("Playlist clip");
307 clipType = i18n("Image clip");
310 clipType = i18n("Slideshow clip");
313 clipType = i18n("Video clip");
315 QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << clipType);
316 item->setIcon(0, KIcon("timeadjust"));
317 item->setText(1, e.attribute("resource"));
318 item->setData(0, hashRole, e.attribute("file_hash"));
319 item->setData(0, sizeRole, e.attribute("_mismatch"));
320 e.removeAttribute("_mismatch");
321 item->setData(0, resetDurationRole, (int) e.hasAttribute("_resetDuration"));
322 e.removeAttribute("_resetDuration");
323 item->setData(0, statusRole, CLIPWRONGDURATION);
324 item->setData(0, typeRole, t);
325 item->setData(0, idRole, e.attribute("id"));
326 item->setToolTip(0, i18n("Duration mismatch"));
329 // Check missing proxies
330 max = missingProxies.count();
332 QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Proxy clip"));
333 item->setIcon(0, KIcon("dialog-warning"));
334 item->setText(1, i18n("%1 missing proxy clips, will be recreated on project opening", max));
335 item->setData(0, hashRole, e.attribute("file_hash"));
336 item->setData(0, statusRole, PROXYMISSING);
337 item->setToolTip(0, i18n("Missing proxy"));
340 for (int i = 0; i < max; i++) {
341 e = missingProxies.at(i).toElement();
343 QString realPath = e.attribute("resource");
344 QString id = e.attribute("id");
345 // Tell Kdenlive to recreate proxy
346 e.setAttribute("_replaceproxy", "1");
347 // Replace proxy url with real clip in MLT producers
348 QDomNodeList properties;
350 QDomElement property;
351 int prodsCount = documentProducers.count();
352 for (int j = 0; j < prodsCount; j++) {
353 mltProd = documentProducers.at(j).toElement();
354 QString prodId = mltProd.attribute("id");
355 bool slowmotion = false;
356 if (prodId.startsWith("slowmotion")) {
358 prodId = prodId.section(':', 1, 1);
360 if (prodId.contains('_')) prodId = prodId.section('_', 0, 0);
362 // Hit, we must replace url
363 properties = mltProd.childNodes();
364 for (int k = 0; k < properties.count(); ++k) {
365 property = properties.item(k).toElement();
366 if (property.attribute("name") == "resource") {
367 QString resource = property.firstChild().nodeValue();
369 if (slowmotion) suffix = "?" + resource.section('?', -1);
370 property.firstChild().setNodeValue(realPath + suffix);
379 // original doc was modified
380 QDomElement infoXml = m_doc.elementsByTagName("kdenlivedoc").at(0).toElement();
381 infoXml.setAttribute("modified", "1");
384 // Check clips with available proxies but missing original source clips
385 max = missingSources.count();
387 QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget, QStringList() << i18n("Source clip"));
388 item->setIcon(0, KIcon("dialog-warning"));
389 item->setText(1, i18n("%1 missing source clips, you can only use the proxies", max));
390 item->setData(0, hashRole, e.attribute("file_hash"));
391 item->setData(0, statusRole, SOURCEMISSING);
392 item->setToolTip(0, i18n("Missing source clip"));
395 for (int i = 0; i < max; i++) {
396 e = missingSources.at(i).toElement();
398 QString realPath = e.attribute("resource");
399 QString id = e.attribute("id");
400 // Tell Kdenlive the source is missing
401 e.setAttribute("_missingsource", "1");
405 // original doc was modified
406 QDomElement infoXml = m_doc.elementsByTagName("kdenlivedoc").at(0).toElement();
407 infoXml.setAttribute("modified", "1");
410 connect(m_ui.recursiveSearch, SIGNAL(pressed()), this, SLOT(slotSearchClips()));
411 connect(m_ui.usePlaceholders, SIGNAL(pressed()), this, SLOT(slotPlaceholders()));
412 connect(m_ui.removeSelected, SIGNAL(pressed()), this, SLOT(slotDeleteSelected()));
413 connect(m_ui.fixDuration, SIGNAL(pressed()), this, SLOT(slotFixDuration()));
414 connect(m_ui.treeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotEditItem(QTreeWidgetItem *, int)));
415 connect(m_ui.treeWidget, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckButtons()));
417 if (m_ui.treeWidget->topLevelItem(0)) m_ui.treeWidget->setCurrentItem(m_ui.treeWidget->topLevelItem(0));
419 int acceptMissing = m_dialog->exec();
420 if (acceptMissing == QDialog::Accepted) acceptDialog();
421 return (acceptMissing != QDialog::Accepted);
424 DocumentChecker::~DocumentChecker()
426 if (m_dialog) delete m_dialog;
430 QString DocumentChecker::getProperty(QDomElement effect, const QString &name)
432 QDomNodeList params = effect.elementsByTagName("property");
433 for (int i = 0; i < params.count(); i++) {
434 QDomElement e = params.item(i).toElement();
435 if (e.attribute("name") == name) {
436 return e.firstChild().nodeValue();
442 void DocumentChecker::setProperty(QDomElement effect, const QString &name, const QString value)
444 QDomNodeList params = effect.elementsByTagName("property");
445 for (int i = 0; i < params.count(); i++) {
446 QDomElement e = params.item(i).toElement();
447 if (e.attribute("name") == name) {
448 e.firstChild().setNodeValue(value);
453 void DocumentChecker::slotSearchClips()
455 QString newpath = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow(), i18n("Clips folder"));
456 if (newpath.isEmpty()) return;
459 m_ui.recursiveSearch->setEnabled(false);
460 QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
461 QDir searchDir(newpath);
463 if (child->data(0, statusRole).toInt() == CLIPMISSING) {
464 QString clipPath = searchFileRecursively(searchDir, child->data(0, sizeRole).toString(), child->data(0, hashRole).toString());
465 if (!clipPath.isEmpty()) {
467 child->setText(1, clipPath);
468 child->setIcon(0, KIcon("dialog-ok"));
469 child->setData(0, statusRole, CLIPOK);
471 } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
472 QString fileName = searchLuma(searchDir, child->data(0, idRole).toString());
473 if (!fileName.isEmpty()) {
475 child->setText(1, fileName);
476 child->setIcon(0, KIcon("dialog-ok"));
477 child->setData(0, statusRole, LUMAOK);
480 else if (child->data(0, typeRole).toInt() == TITLE_IMAGE_ELEMENT && child->data(0, statusRole).toInt() == CLIPPLACEHOLDER) {
481 // Search missing title images
482 QString missingFileName = KUrl(child->text(1)).fileName();
483 QString newPath = searchPathRecursively(searchDir, missingFileName);
484 if (!newPath.isEmpty()) {
487 child->setText(1, newPath);
488 child->setIcon(0, KIcon("dialog-ok"));
489 child->setData(0, statusRole, CLIPOK);
493 child = m_ui.treeWidget->topLevelItem(ix);
495 m_ui.recursiveSearch->setEnabled(true);
497 // original doc was modified
498 QDomElement infoXml = m_doc.elementsByTagName("kdenlivedoc").at(0).toElement();
499 infoXml.setAttribute("modified", "1");
505 QString DocumentChecker::searchLuma(const QDir &dir, const QString &file) const
507 KUrl searchPath(KdenliveSettings::mltpath());
508 QString fname = KUrl(file).fileName();
509 if (file.contains("PAL"))
510 searchPath.cd("../lumas/PAL");
512 searchPath.cd("../lumas/NTSC");
513 QString result = searchPath.path(KUrl::AddTrailingSlash) + fname;
514 if (QFile::exists(result))
516 // try to find luma in application path
518 searchPath = KUrl(QCoreApplication::applicationDirPath());
519 searchPath.cd("../share/apps/kdenlive/lumas");
520 result = searchPath.path(KUrl::AddTrailingSlash) + fname;
521 if (QFile::exists(result))
523 // Try in Kdenlive's standard KDE path
524 result = KStandardDirs::locate("appdata", "lumas/" + fname);
525 if (!result.isEmpty()) return result;
526 // Try in user's chosen folder
527 return searchPathRecursively(dir, fname);
530 QString DocumentChecker::searchPathRecursively(const QDir &dir, const QString &fileName) const
532 QString foundFileName;
536 searchDir.setNameFilters(filters);
537 QStringList filesAndDirs = searchDir.entryList(QDir::Files | QDir::Readable);
538 if (!filesAndDirs.isEmpty()) return searchDir.absoluteFilePath(filesAndDirs.at(0));
539 searchDir.setNameFilters(QStringList());
540 filesAndDirs = searchDir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
541 for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
542 foundFileName = searchPathRecursively(searchDir.absoluteFilePath(filesAndDirs.at(i)), fileName);
543 if (!foundFileName.isEmpty())
546 return foundFileName;
549 QString DocumentChecker::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const
551 QString foundFileName;
554 QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable);
555 for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
556 QFile file(dir.absoluteFilePath(filesAndDirs.at(i)));
557 if (QString::number(file.size()) == matchSize) {
558 if (file.open(QIODevice::ReadOnly)) {
560 * 1 MB = 1 second per 450 files (or faster)
561 * 10 MB = 9 seconds per 450 files (or faster)
563 if (file.size() > 1000000 * 2) {
564 fileData = file.read(1000000);
565 if (file.seek(file.size() - 1000000))
566 fileData.append(file.readAll());
568 fileData = file.readAll();
570 fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
571 if (QString(fileHash.toHex()) == matchHash)
572 return file.fileName();
575 //kDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex();
577 filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
578 for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
579 foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash);
580 if (!foundFileName.isEmpty())
583 return foundFileName;
586 void DocumentChecker::slotEditItem(QTreeWidgetItem *item, int)
588 int t = item->data(0, typeRole).toInt();
589 if (t == TITLE_FONT_ELEMENT) return;
590 //|| t == TITLE_IMAGE_ELEMENT) {
592 KUrl url = KUrlRequesterDialog::getUrl(item->text(1), m_dialog, i18n("Enter new location for file"));
593 if (url.isEmpty()) return;
594 item->setText(1, url.path());
595 if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) {
596 item->setIcon(0, KIcon("dialog-ok"));
597 int id = item->data(0, statusRole).toInt();
598 if (id < 10) item->setData(0, statusRole, CLIPOK);
599 else item->setData(0, statusRole, LUMAOK);
602 item->setIcon(0, KIcon("dialog-close"));
603 int id = item->data(0, statusRole).toInt();
604 if (id < 10) item->setData(0, statusRole, CLIPMISSING);
605 else item->setData(0, statusRole, LUMAMISSING);
611 void DocumentChecker::acceptDialog()
613 QDomElement e, property;
614 QDomNodeList producers = m_doc.elementsByTagName("producer");
615 QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
616 QDomNodeList properties;
619 // prepare transitions
620 QDomNodeList trans = m_doc.elementsByTagName("transition");
622 // Mark document as modified
623 m_doc.documentElement().setAttribute("modified", 1);
625 QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
627 int t = child->data(0, typeRole).toInt();
628 if (child->data(0, statusRole).toInt() == CLIPOK) {
629 QString id = child->data(0, idRole).toString();
630 if (t == TITLE_IMAGE_ELEMENT) {
631 // edit images embedded in titles
632 for (int i = 0; i < infoproducers.count(); i++) {
633 e = infoproducers.item(i).toElement();
634 if (e.attribute("id") == id) {
636 QString xml = e.attribute("xmldata");
637 xml.replace(child->data(0, typeOriginalResource).toString(), child->text(1));
638 e.setAttribute("xmldata", xml);
642 for (int i = 0; i < producers.count(); i++) {
643 e = producers.item(i).toElement();
644 if (e.attribute("id").section('_', 0, 0) == id) {
646 properties = e.childNodes();
647 for (int j = 0; j < properties.count(); ++j) {
648 property = properties.item(j).toElement();
649 if (property.attribute("name") == "xmldata") {
650 QString xml = property.firstChild().nodeValue();
651 xml.replace(child->data(0, typeOriginalResource).toString(), child->text(1));
652 property.firstChild().setNodeValue(xml);
660 for (int i = 0; i < infoproducers.count(); i++) {
661 e = infoproducers.item(i).toElement();
662 if (e.attribute("id") == id) {
664 e.setAttribute("resource", child->text(1));
665 e.setAttribute("name", KUrl(child->text(1)).fileName());
669 for (int i = 0; i < producers.count(); i++) {
670 e = producers.item(i).toElement();
671 if (e.attribute("id").section('_', 0, 0) == id || e.attribute("id").section(':', 1, 1) == id) {
673 properties = e.childNodes();
674 for (int j = 0; j < properties.count(); ++j) {
675 property = properties.item(j).toElement();
676 if (property.attribute("name") == "resource") {
677 QString resource = property.firstChild().nodeValue();
678 if (resource.contains(QRegExp("\\?[0-9]+\\.[0-9]+(&strobe=[0-9]+)?$")))
679 property.firstChild().setNodeValue(child->text(1) + '?' + resource.section('?', -1));
681 property.firstChild().setNodeValue(child->text(1));
688 } else if (child->data(0, statusRole).toInt() == CLIPPLACEHOLDER && t != TITLE_FONT_ELEMENT && t != TITLE_IMAGE_ELEMENT) {
689 QString id = child->data(0, idRole).toString();
690 for (int i = 0; i < infoproducers.count(); i++) {
691 e = infoproducers.item(i).toElement();
692 if (e.attribute("id") == id) {
694 e.setAttribute("placeholder", '1');
698 } else if (child->data(0, statusRole).toInt() == LUMAOK) {
699 for (int i = 0; i < trans.count(); i++) {
700 QString luma = getProperty(trans.at(i).toElement(), "luma");
702 kDebug() << "luma: " << luma;
703 if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) {
704 setProperty(trans.at(i).toElement(), "luma", child->text(1));
705 kDebug() << "replace with; " << child->text(1);
708 } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
709 for (int i = 0; i < trans.count(); i++) {
710 QString luma = getProperty(trans.at(i).toElement(), "luma");
711 if (!luma.isEmpty() && luma == child->data(0, idRole).toString()) {
712 setProperty(trans.at(i).toElement(), "luma", QString());
717 child = m_ui.treeWidget->topLevelItem(ix);
722 void DocumentChecker::slotPlaceholders()
725 QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
727 if (child->data(0, statusRole).toInt() == CLIPMISSING) {
728 child->setData(0, statusRole, CLIPPLACEHOLDER);
729 child->setIcon(0, KIcon("dialog-ok"));
730 } else if (child->data(0, statusRole).toInt() == LUMAMISSING) {
731 child->setData(0, statusRole, LUMAPLACEHOLDER);
732 child->setIcon(0, KIcon("dialog-ok"));
735 child = m_ui.treeWidget->topLevelItem(ix);
740 void DocumentChecker::slotFixDuration()
743 QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
744 QDomNodeList documentProducers = m_doc.elementsByTagName("producer");
746 if (child->data(0, statusRole).toInt() == CLIPWRONGDURATION) {
747 QString id = child->data(0, idRole).toString();
748 bool resetDuration = child->data(0, resetDurationRole).toInt();
750 for (int i = 0; i < m_info.count(); i++) {
751 QDomElement e = m_info.at(i).toElement();
752 if (e.attribute("id") == id) {
753 if (m_missingClips.contains(e)) {
754 // we cannot fix duration of missing clips
755 resetDuration = false;
758 if (resetDuration) e.removeAttribute("duration");
759 else e.setAttribute("duration", child->data(0, sizeRole).toString());
760 child->setData(0, statusRole, CLIPOK);
761 child->setIcon(0, KIcon("dialog-ok"));
767 // something is wrong in clip durations, so remove them so mlt fetches them again
768 for (int j = 0; j < documentProducers.count(); j++) {
769 QDomElement mltProd = documentProducers.at(j).toElement();
770 QString prodId = mltProd.attribute("id");
771 if (prodId == id || prodId.startsWith(id + "_")) {
772 EffectsList::removeProperty(mltProd, "length");
778 child = m_ui.treeWidget->topLevelItem(ix);
780 QDomElement infoXml = m_doc.elementsByTagName("kdenlivedoc").at(0).toElement();
781 infoXml.setAttribute("modified", "1");
782 m_ui.fixDuration->setEnabled(false);
787 void DocumentChecker::checkStatus()
791 QTreeWidgetItem *child = m_ui.treeWidget->topLevelItem(ix);
793 int status = child->data(0, statusRole).toInt();
794 if (status == CLIPMISSING || status == LUMAMISSING || status == CLIPWRONGDURATION) {
799 child = m_ui.treeWidget->topLevelItem(ix);
801 m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
805 void DocumentChecker::slotDeleteSelected()
807 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)
809 QStringList deletedIds;
810 QStringList deletedLumas;
811 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
813 foreach(QTreeWidgetItem *child, m_ui.treeWidget->selectedItems()) {
814 int id = child->data(0, statusRole).toInt();
815 if (id == CLIPMISSING) {
816 deletedIds.append(child->data(0, idRole).toString());
819 else if (id == LUMAMISSING) {
820 deletedLumas.append(child->data(0, idRole).toString());
825 if (!deletedLumas.isEmpty()) {
827 QDomNodeList transitions = m_doc.elementsByTagName("transition");
828 foreach (QString lumaPath, deletedLumas) {
829 for (int i = 0; i < transitions.count(); i++) {
830 e = transitions.item(i).toElement();
831 QString resource = EffectsList::property(e, "luma");
832 if (resource == lumaPath) EffectsList::removeProperty(e, "luma");
837 if (!deletedIds.isEmpty()) {
839 QDomNodeList producers = m_doc.elementsByTagName("producer");
840 QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
842 QDomNode mlt = m_doc.elementsByTagName("mlt").at(0);
843 QDomNode kdenlivedoc = m_doc.elementsByTagName("kdenlivedoc").at(0);
845 for (int i = 0, j = 0; i < infoproducers.count() && j < deletedIds.count(); i++) {
846 e = infoproducers.item(i).toElement();
847 if (deletedIds.contains(e.attribute("id"))) {
849 kdenlivedoc.removeChild(e);
855 for (int i = 0; i < producers.count(); i++) {
856 e = producers.item(i).toElement();
857 if (deletedIds.contains(e.attribute("id").section('_', 0, 0)) || deletedIds.contains(e.attribute("id").section(':', 1, 1).section('_', 0, 0))) {
864 for (int i = 0; i < playlists.count(); i++) {
865 QDomNodeList entries = playlists.at(i).toElement().elementsByTagName("entry");
866 for (int j = 0; j < entries.count(); j++) {
867 e = entries.item(j).toElement();
868 if (deletedIds.contains(e.attribute("producer").section('_', 0, 0)) || deletedIds.contains(e.attribute("producer").section(':', 1, 1).section('_', 0, 0))) {
869 // Replace clip with blank
870 while (e.childNodes().count() > 0)
871 e.removeChild(e.firstChild());
872 e.setTagName("blank");
873 e.removeAttribute("producer");
874 int length = e.attribute("out").toInt() - e.attribute("in").toInt();
875 e.setAttribute("length", length);
880 QDomElement infoXml = m_doc.elementsByTagName("kdenlivedoc").at(0).toElement();
881 infoXml.setAttribute("modified", "1");
886 void DocumentChecker::checkMissingImagesAndFonts(QStringList images, QStringList fonts, const QString &id, const QString &baseClip)
889 foreach(const QString &img, images) {
890 if (m_safeImages.contains(img)) continue;
891 if (!KIO::NetAccess::exists(KUrl(img), KIO::NetAccess::SourceSide, 0)) {
892 QDomElement e = doc.createElement("missingclip");
893 e.setAttribute("type", TITLE_IMAGE_ELEMENT);
894 e.setAttribute("resource", img);
895 e.setAttribute("id", id);
896 e.setAttribute("name", baseClip);
897 m_missingClips.append(e);
899 else m_safeImages.append(img);
901 foreach(const QString &fontelement, fonts) {
902 if (m_safeFonts.contains(fontelement)) continue;
903 QFont f(fontelement);
904 //kDebug() << "/ / / CHK FONTS: " << fontelement << " = " << QFontInfo(f).family();
905 if (fontelement != QFontInfo(f).family()) {
906 QDomElement e = doc.createElement("missingclip");
907 e.setAttribute("type", TITLE_FONT_ELEMENT);
908 e.setAttribute("resource", fontelement);
909 e.setAttribute("id", id);
910 e.setAttribute("name", baseClip);
911 m_missingClips.append(e);
913 else m_safeFonts.append(fontelement);
918 void DocumentChecker::slotCheckButtons()
920 if (m_ui.treeWidget->currentItem()) {
921 QTreeWidgetItem *item = m_ui.treeWidget->currentItem();
922 int t = item->data(0, typeRole).toInt();
923 int s = item->data(0, statusRole).toInt();
924 if (t == TITLE_FONT_ELEMENT || t == TITLE_IMAGE_ELEMENT || s == PROXYMISSING) {
925 m_ui.removeSelected->setEnabled(false);
926 } else m_ui.removeSelected->setEnabled(true);
931 #include "documentchecker.moc"