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