]> git.sesse.net Git - kdenlive/blob - src/archivewidget.cpp
progress on archive project (slideshows not implemented yet)
[kdenlive] / src / archivewidget.cpp
1 /***************************************************************************
2  *   Copyright (C) 2011 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 "archivewidget.h"
22 #include "titlewidget.h"
23
24 #include <KLocale>
25 #include <KDiskFreeSpaceInfo>
26 #include <KUrlRequester>
27 #include <KFileDialog>
28 #include <KMessageBox>
29 #include <KApplication>
30 #include <KIO/NetAccess>
31
32 #include <KDebug>
33 #include <QTreeWidget>
34 #include "projectsettings.h"
35
36
37 ArchiveWidget::ArchiveWidget(QDomDocument doc, QList <DocClipBase*> list, QStringList luma_list, QWidget * parent) :
38         QDialog(parent),
39         m_requestedSize(0),
40         m_copyJob(NULL),
41         m_doc(doc)
42 {
43     setupUi(this);
44     setWindowTitle(i18n("Archive Project"));
45     archive_url->setUrl(KUrl(QDir::homePath()));
46     connect(archive_url, SIGNAL(textChanged (const QString &)), this, SLOT(slotCheckSpace()));
47
48     // Setup categories
49     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
50     videos->setIcon(0, KIcon("video-x-generic"));
51     videos->setData(0, Qt::UserRole, "videos");
52     videos->setExpanded(false);
53     QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
54     sounds->setIcon(0, KIcon("audio-x-generic"));
55     sounds->setData(0, Qt::UserRole, "sounds");
56     sounds->setExpanded(false);
57     QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
58     images->setIcon(0, KIcon("image-x-generic"));
59     images->setData(0, Qt::UserRole, "images");
60     images->setExpanded(false);
61     QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
62     slideshows->setIcon(0, KIcon("image-x-generic"));
63     slideshows->setData(0, Qt::UserRole, "slideshows");
64     slideshows->setExpanded(false);
65     QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
66     texts->setIcon(0, KIcon("text-plain"));
67     texts->setData(0, Qt::UserRole, "texts");
68     texts->setExpanded(false);
69     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
70     others->setIcon(0, KIcon("unknown"));
71     others->setData(0, Qt::UserRole, "others");
72     others->setExpanded(false);
73     QTreeWidgetItem *lumas = new QTreeWidgetItem(files_list, QStringList() << i18n("Luma files"));
74     lumas->setIcon(0, KIcon("image-x-generic"));
75     lumas->setData(0, Qt::UserRole, "lumas");
76     lumas->setExpanded(false);
77     
78     QTreeWidgetItem *proxies = new QTreeWidgetItem(files_list, QStringList() << i18n("Proxy clips"));
79     proxies->setIcon(0, KIcon("video-x-generic"));
80     proxies->setData(0, Qt::UserRole, "proxy");
81     proxies->setExpanded(false);
82     
83     // process all files
84     QStringList allFonts;
85     KUrl::List fileUrls;
86     QStringList fileNames;
87     generateItems(lumas, luma_list);
88
89     QStringList slideUrls;
90     QStringList audioUrls;
91     QStringList videoUrls;
92     QStringList imageUrls;
93     QStringList otherUrls;
94     QStringList proxyUrls;
95
96     for (int i = 0; i < list.count(); i++) {
97         DocClipBase *clip = list.at(i);
98         CLIPTYPE t = clip->clipType();
99         if (t == SLIDESHOW) {
100             QStringList subfiles = ProjectSettings::extractSlideshowUrls(clip->fileURL());
101             foreach(const QString & file, subfiles) {
102                 kDebug()<<"SLIDE: "<<file;
103                 new QTreeWidgetItem(slideshows, QStringList() << file);
104                 m_requestedSize += QFileInfo(file).size();
105             }
106         }
107         else if (t == IMAGE) imageUrls << clip->fileURL().path();
108         else if (t == TEXT) {
109             QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
110             QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
111             imageUrls << imagefiles;
112             allFonts << fonts;
113         } else if (t == PLAYLIST) {
114             QStringList files = ProjectSettings::extractPlaylistUrls(clip->fileURL().path());
115             otherUrls << files;
116         }
117         else if (!clip->fileURL().isEmpty()) {
118             if (t == AUDIO) audioUrls << clip->fileURL().path();
119             else {
120                 videoUrls << clip->fileURL().path();
121                 // Check if we have a proxy
122                 QString proxy = clip->getProperty("proxy");
123                 if (!proxy.isEmpty() && proxy != "-" && QFile::exists(proxy)) proxyUrls << proxy;
124             }
125         }
126     }
127
128     generateItems(sounds, audioUrls);
129     generateItems(videos, videoUrls);
130     generateItems(images, imageUrls);
131     //generateItems(slideshows, slideUrls);
132     generateItems(others, otherUrls);
133     generateItems(proxies, proxyUrls);
134     
135 #if QT_VERSION >= 0x040500
136     allFonts.removeDuplicates();
137 #endif
138
139     //TODO: fonts
140
141     // Hide unused categories, add item count
142     int total = 0;
143     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
144         int items = files_list->topLevelItem(i)->childCount();
145         if (items == 0) {
146             files_list->topLevelItem(i)->setHidden(true);
147         }
148         else {
149             total += items;
150             files_list->topLevelItem(i)->setText(0, files_list->topLevelItem(i)->text(0) + " " + i18n("(%1 items)", items));
151         }
152     }
153     
154     project_files->setText(i18n("%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize)));
155     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
156     connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartArchiving()));
157     buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
158     
159     slotCheckSpace();
160 }
161
162 ArchiveWidget::~ArchiveWidget()
163 {
164 }
165
166 void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items)
167 {
168     QStringList filesList;
169     QString fileName;
170     foreach(const QString & file, items) {
171         QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
172         fileName = KUrl(file).fileName();
173         if (filesList.contains(fileName)) {
174             // we have 2 files with same name
175             int ix = 0;
176             QString newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
177             while (filesList.contains(newFileName)) {
178                 ix ++;
179                 newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
180             }
181             fileName = newFileName;
182             item->setData(0, Qt::UserRole, fileName);
183         }
184         filesList << fileName;
185         m_requestedSize += QFileInfo(file).size();
186     }
187 }
188
189 void ArchiveWidget::slotCheckSpace()
190 {
191     KDiskFreeSpaceInfo inf = KDiskFreeSpaceInfo::freeSpaceInfo( archive_url->url().path());
192     KIO::filesize_t freeSize = inf.available();;
193     if (freeSize > m_requestedSize) {
194         // everything is ok
195         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
196         icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
197         text_info->setText(i18n("Available space on drive: %1", KIO::convertSize(freeSize)));
198     }
199     else {
200         buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
201         icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
202         text_info->setText(i18n("Not enough space on drive, free space: %1", KIO::convertSize(freeSize)));
203     }
204 }
205
206 bool ArchiveWidget::slotStartArchiving(bool firstPass)
207 {
208     if (firstPass && m_copyJob) {
209         // archiving in progress, abort
210         m_copyJob->kill(KJob::EmitResult);
211         return true;
212     }
213     if (!firstPass) m_copyJob = NULL;
214     else {
215         //starting archiving
216         m_duplicateFiles.clear();
217         m_replacementList.clear();
218     }
219     KUrl::List files;
220     KUrl destUrl;
221     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
222         if (files_list->topLevelItem(i)->childCount() > 0 && !files_list->topLevelItem(i)->isDisabled()) {
223             files_list->setCurrentItem(files_list->topLevelItem(i));
224             files_list->topLevelItem(i)->setDisabled(true);
225             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + files_list->topLevelItem(i)->data(0, Qt::UserRole).toString());
226             KIO::NetAccess::mkdir(destUrl, this);
227             QTreeWidgetItem *item;
228             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
229                 item = files_list->topLevelItem(i)->child(j);
230                 if (item->data(0, Qt::UserRole).isNull()) {
231                     files << KUrl(item->text(0));
232                 }
233                 else {
234                     // We must rename the destination file, since another file with same name exists
235                     //TODO: monitor progress
236                     m_duplicateFiles.insert(KUrl(item->text(0)), KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString()));
237                 }
238             }
239             break;
240         }
241     }
242
243     if (destUrl.isEmpty()) {
244         if (m_duplicateFiles.isEmpty()) return false;        
245         QMapIterator<KUrl, KUrl> i(m_duplicateFiles);
246         KUrl startJob;
247         if (i.hasNext()) {
248             i.next();
249             startJob = i.key();
250             KIO::CopyJob *job = KIO::copyAs(startJob, i.value(), KIO::HideProgressInfo);
251             connect(job, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
252             connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
253             m_duplicateFiles.remove(startJob);
254         }
255         return true;
256     }
257
258     m_copyJob = KIO::copy (files, destUrl, KIO::HideProgressInfo);
259     connect(m_copyJob, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
260     connect(m_copyJob, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
261
262     if (firstPass) {
263         progressBar->setValue(0);
264         buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
265     }
266     return true;
267 }
268
269 void ArchiveWidget::slotArchivingFinished(KJob *job)
270 {
271     if (job->error() == 0) {
272         if (slotStartArchiving(false)) {
273             // We still have files to archive
274             return;
275         }
276         else {
277             // Archiving finished
278             progressBar->setValue(100);
279             if (processProjectFile()) {
280                 icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
281                 text_info->setText(i18n("Project was successfully archived."));
282             }
283             else {
284                 icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
285                 text_info->setText(i18n("There was an error processing project file"));
286             }
287         }
288     }
289     else {
290         m_copyJob = NULL;
291         icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
292         text_info->setText(i18n("There was an error while copying the files: %1", job->errorString()));
293     }
294     buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
295     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
296         files_list->topLevelItem(i)->setDisabled(false);
297     }
298 }
299
300 void ArchiveWidget::slotArchivingProgress(KJob *, qulonglong size)
301 {
302     progressBar->setValue((int) 100 * size / m_requestedSize);
303 }
304
305
306 bool ArchiveWidget::processProjectFile()
307 {
308     KUrl destUrl;
309     QTreeWidgetItem *item;
310     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
311         if (files_list->topLevelItem(i)->childCount() > 0) {
312             destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + files_list->topLevelItem(i)->data(0, Qt::UserRole).toString());
313             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
314                 item = files_list->topLevelItem(i)->child(j);
315                 KUrl src(item->text(0));
316                 KUrl dest = destUrl;
317                 if (item->data(0, Qt::UserRole).isNull()) {
318                     dest.addPath(src.fileName());
319                 }
320                 else {
321                     dest.addPath(item->data(0, Qt::UserRole).toString());
322                 }
323                 m_replacementList.insert(src, dest);
324             }
325         }
326     }
327     
328     // process kdenlive producers           
329     QDomElement mlt = m_doc.documentElement();
330     QString root = mlt.attribute("root") + "/";
331     
332     QDomNodeList prods = mlt.elementsByTagName("kdenlive_producer");
333     for (int i = 0; i < prods.count(); i++) {
334         QDomElement e = prods.item(i).toElement();
335         if (e.isNull()) continue;
336         if (e.hasAttribute("resource")) {
337             KUrl src(e.attribute("resource"));
338             KUrl dest = m_replacementList.value(src);
339             if (!dest.isEmpty()) e.setAttribute("resource", dest.path());
340         }
341         if (e.hasAttribute("proxy") && e.attribute("proxy") != "-") {
342             KUrl src(e.attribute("proxy"));
343             KUrl dest = m_replacementList.value(src);
344             if (!dest.isEmpty()) e.setAttribute("proxy", dest.path());
345         }
346     }
347
348     // process mlt producers
349     prods = mlt.elementsByTagName("producer");
350     for (int i = 0; i < prods.count(); i++) {
351         QDomElement e = prods.item(i).toElement();
352         if (e.isNull()) continue;
353         QString src = EffectsList::property(e, "resource");
354         if (!src.isEmpty()) {
355             if (!src.startsWith('/')) src.prepend(root);
356             KUrl srcUrl(src);
357             KUrl dest = m_replacementList.value(src);
358             if (!dest.isEmpty()) EffectsList::setProperty(e, "resource", dest.path());
359         }
360     }
361
362     // process mlt transitions (for luma files)
363     prods = mlt.elementsByTagName("transition");
364     QString attribute;
365     for (int i = 0; i < prods.count(); i++) {
366         QDomElement e = prods.item(i).toElement();
367         if (e.isNull()) continue;
368         attribute = "resource";
369         QString src = EffectsList::property(e, attribute);
370         if (src.isEmpty()) attribute = "luma";
371         src = EffectsList::property(e, attribute);
372         if (!src.isEmpty()) {
373             if (!src.startsWith('/')) src.prepend(root);
374             KUrl srcUrl(src);
375             KUrl dest = m_replacementList.value(src);
376             if (!dest.isEmpty()) EffectsList::setProperty(e, attribute, dest.path());
377         }
378     }
379
380     QString path = archive_url->url().path(KUrl::AddTrailingSlash) + "project.kdenlive";
381     QFile file(path);
382     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
383         kWarning() << "//////  ERROR writing to file: " << path;
384         KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1", path));
385         return false;
386     }
387
388     file.write(m_doc.toString().toUtf8());
389     if (file.error() != QFile::NoError) {
390         KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1", path));
391         file.close();
392         return false;
393     }
394     file.close();
395     return true;
396 }
397