]> git.sesse.net Git - kdenlive/blob - src/documentchecker.cpp
a39f1037ff61adc06b33fad13d320468cf97bb53
[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 "definitions.h"
24
25 #include <KDebug>
26 #include <KGlobalSettings>
27 #include <KFileItem>
28 #include <KIO/NetAccess>
29 #include <KFileDialog>
30 #include <KApplication>
31 #include <KUrlRequesterDialog>
32 #include <KMessageBox>
33
34 #include <QTreeWidgetItem>
35 #include <QFile>
36 #include <QHeaderView>
37 #include <QIcon>
38 #include <QPixmap>
39 #include <QTimer>
40 #include <QCryptographicHash>
41
42 const int hashRole = Qt::UserRole;
43 const int sizeRole = Qt::UserRole + 1;
44 const int idRole = Qt::UserRole + 2;
45 const int statusRole = Qt::UserRole + 3;
46
47 const int CLIPMISSING = 0;
48 const int CLIPOK = 1;
49 const int CLIPPLACEHOLDER = 2;
50
51 DocumentChecker::DocumentChecker(QDomDocument doc, QWidget * parent) :
52         QDialog(parent), m_doc(doc)
53 {
54     setFont(KGlobalSettings::toolBarFont());
55     m_view.setupUi(this);
56
57     QDomNodeList producers = m_doc.elementsByTagName("producer");
58     QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
59
60     int clipType;
61     QDomElement e;
62     QString id;
63     QString resource;
64     QList <QDomElement> missingClips;
65     for (int i = 0; i < infoproducers.count(); i++) {
66         e = infoproducers.item(i).toElement();
67         clipType = e.attribute("type").toInt();
68         if (clipType == TEXT) continue;
69         id = e.attribute("id");
70         resource = e.attribute("resource");
71         if (clipType == SLIDESHOW) resource = KUrl(resource).directory();
72         if (!KIO::NetAccess::exists(KUrl(resource), KIO::NetAccess::SourceSide, 0)) {
73             // Missing clip found
74             missingClips.append(e);
75         }
76     }
77
78     if (missingClips.isEmpty()) QTimer::singleShot(0, this, SLOT(accept()));
79     m_view.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
80     for (int i = 0; i < missingClips.count(); i++) {
81         e = missingClips.at(i).toElement();
82         QString clipType;
83         switch (e.attribute("type").toInt()) {
84         case AV:
85             clipType = i18n("Video clip");
86             break;
87         case VIDEO:
88             clipType = i18n("Mute video clip");
89             break;
90         case AUDIO:
91             clipType = i18n("Audio clip");
92             break;
93         case PLAYLIST:
94             clipType = i18n("Playlist clip");
95             break;
96         case IMAGE:
97             clipType = i18n("Image clip");
98             break;
99         case SLIDESHOW:
100             clipType = i18n("Slideshow clip");
101             break;
102         default:
103             clipType = i18n("Video clip");
104         }
105         QTreeWidgetItem *item = new QTreeWidgetItem(m_view.treeWidget, QStringList() << clipType << e.attribute("resource"));
106         item->setIcon(0, KIcon("dialog-close"));
107         item->setData(0, hashRole, e.attribute("file_hash"));
108         item->setData(0, sizeRole, e.attribute("file_size"));
109         item->setData(0, idRole, e.attribute("id"));
110         item->setData(0, statusRole, CLIPMISSING);
111     }
112     connect(m_view.recursiveSearch, SIGNAL(pressed()), this, SLOT(slotSearchClips()));
113     connect(m_view.usePlaceholders, SIGNAL(pressed()), this, SLOT(slotPlaceholders()));
114     connect(m_view.removeSelected, SIGNAL(pressed()), this, SLOT(slotDeleteSelected()));
115     connect(m_view.treeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotEditItem(QTreeWidgetItem *, int)));
116     //adjustSize();
117 }
118
119 DocumentChecker::~DocumentChecker() {}
120
121 void DocumentChecker::slotSearchClips()
122 {
123     QString newpath = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow(), i18n("Clips folder"));
124     if (newpath.isEmpty()) return;
125     int ix = 0;
126     QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
127     while (child && child->data(0, statusRole).toInt() == CLIPMISSING) {
128         QString clipPath = searchFileRecursively(QDir(newpath), child->data(0, sizeRole).toString(), child->data(0, hashRole).toString());
129         if (!clipPath.isEmpty()) {
130             child->setText(1, clipPath);
131             child->setIcon(0, KIcon("dialog-ok"));
132             child->setData(0, statusRole, CLIPOK);
133         }
134         ix++;
135         child = m_view.treeWidget->topLevelItem(ix);
136     }
137     checkStatus();
138 }
139
140 QString DocumentChecker::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const
141 {
142     QString foundFileName;
143     QByteArray fileData;
144     QByteArray fileHash;
145     QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable);
146     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
147         QFile file(dir.absoluteFilePath(filesAndDirs.at(i)));
148         if (file.open(QIODevice::ReadOnly)) {
149             if (QString::number(file.size()) == matchSize) {
150                 /*
151                 * 1 MB = 1 second per 450 files (or faster)
152                 * 10 MB = 9 seconds per 450 files (or faster)
153                 */
154                 if (file.size() > 1000000*2) {
155                     fileData = file.read(1000000);
156                     if (file.seek(file.size() - 1000000))
157                         fileData.append(file.readAll());
158                 } else
159                     fileData = file.readAll();
160                 file.close();
161                 fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
162                 if (QString(fileHash.toHex()) == matchHash)
163                     return file.fileName();
164             }
165         }
166         kDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex();
167     }
168     filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
169     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
170         foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash);
171         if (!foundFileName.isEmpty())
172             break;
173     }
174     return foundFileName;
175 }
176
177 void DocumentChecker::slotEditItem(QTreeWidgetItem *item, int)
178 {
179     KUrl url = KUrlRequesterDialog::getUrl(item->text(1), this, i18n("Enter new location for file"));
180     if (url.isEmpty()) return;
181     item->setText(1, url.path());
182     if (KIO::NetAccess::exists(url, KIO::NetAccess::SourceSide, 0)) {
183         item->setIcon(0, KIcon("dialog-ok"));
184         item->setData(0, statusRole, CLIPOK);
185         checkStatus();
186     }
187 }
188
189 // virtual
190 void DocumentChecker::accept()
191 {
192     QDomElement e;
193     QDomNodeList producers = m_doc.elementsByTagName("producer");
194     QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
195     int ix = 0;
196     QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
197     while (child) {
198         if (child->data(0, statusRole).toInt() == CLIPOK) {
199             QString id = child->data(0, idRole).toString();
200             for (int i = 0; i < infoproducers.count(); i++) {
201                 e = infoproducers.item(i).toElement();
202                 if (e.attribute("id") == id) {
203                     // Fix clip
204                     e.setAttribute("resource", child->text(1));
205                     break;
206                 }
207             }
208             for (int i = 0; i < producers.count(); i++) {
209                 e = producers.item(i).toElement();
210                 if (e.attribute("id") == id) {
211                     // Fix clip
212                     e.setAttribute("resource", child->text(1));
213                     break;
214                 }
215             }
216         } else if (child->data(0, statusRole).toInt() == CLIPPLACEHOLDER) {
217             QString id = child->data(0, idRole).toString();
218             for (int i = 0; i < infoproducers.count(); i++) {
219                 e = infoproducers.item(i).toElement();
220                 if (e.attribute("id") == id) {
221                     // Fix clip
222                     e.setAttribute("placeholder", '1');
223                     break;
224                 }
225             }
226         }
227         ix++;
228         child = m_view.treeWidget->topLevelItem(ix);
229     }
230     QDialog::accept();
231 }
232
233 void DocumentChecker::slotPlaceholders()
234 {
235     int ix = 0;
236     QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
237     while (child) {
238         if (child->data(0, statusRole).toInt() == CLIPMISSING) {
239             child->setData(0, statusRole, CLIPPLACEHOLDER);
240             child->setIcon(0, KIcon("dialog-ok"));
241         }
242         ix++;
243         child = m_view.treeWidget->topLevelItem(ix);
244     }
245     checkStatus();
246 }
247
248
249 void DocumentChecker::checkStatus()
250 {
251     bool status = true;
252     int ix = 0;
253     QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
254     while (child) {
255         if (child->data(0, statusRole).toInt() == CLIPMISSING) {
256             status = false;
257             break;
258         }
259         ix++;
260         child = m_view.treeWidget->topLevelItem(ix);
261     }
262     m_view.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
263 }
264
265
266 void DocumentChecker::slotDeleteSelected()
267 {
268     if (KMessageBox::warningContinueCancel(this, i18n("This will remove the selected clips from this project"), i18n("Remove clips")) == KMessageBox::Cancel) return;
269     int ix = 0;
270     QStringList deletedIds;
271     QTreeWidgetItem *child = m_view.treeWidget->topLevelItem(ix);
272     QDomNodeList playlists = m_doc.elementsByTagName("playlist");
273
274     while (child) {
275         if (child->isSelected()) {
276             QString id = child->data(0, idRole).toString();
277             deletedIds.append(id);
278             for (int j = 0; j < playlists.count(); j++)
279                 deletedIds.append(id + '_' + QString::number(j));
280             delete child;
281         } else ix++;
282         child = m_view.treeWidget->topLevelItem(ix);
283     }
284     kDebug() << "// Clips to delete: " << deletedIds;
285
286     if (!deletedIds.isEmpty()) {
287         QDomElement e;
288         QDomNodeList producers = m_doc.elementsByTagName("producer");
289         QDomNodeList infoproducers = m_doc.elementsByTagName("kdenlive_producer");
290
291         QDomElement westley = m_doc.firstChildElement("westley");
292         QDomElement kdenlivedoc = westley.firstChildElement("kdenlivedoc");
293
294         for (int i = 0; i < infoproducers.count(); i++) {
295             e = infoproducers.item(i).toElement();
296             if (deletedIds.contains(e.attribute("id"))) {
297                 // Remove clip
298                 kdenlivedoc.removeChild(e);
299                 break;
300             }
301         }
302
303         for (int i = 0; i < producers.count(); i++) {
304             e = producers.item(i).toElement();
305             if (deletedIds.contains(e.attribute("id"))) {
306                 // Remove clip
307                 westley.removeChild(e);
308                 break;
309             }
310         }
311
312         for (int i = 0; i < playlists.count(); i++) {
313             QDomNodeList entries = playlists.at(i).toElement().elementsByTagName("entry");
314             for (int j = 0; j < playlists.count(); j++) {
315                 e = entries.item(j).toElement();
316                 if (deletedIds.contains(e.attribute("producer"))) {
317                     // Replace clip with blank
318                     e.setTagName("blank");
319                     e.removeAttribute("producer");
320                     int length = e.attribute("out").toInt() - e.attribute("in").toInt();
321                     e.setAttribute("length", length);
322                 }
323             }
324         }
325         checkStatus();
326     }
327 }
328
329 #include "documentchecker.moc"
330
331