1 /***************************************************************************
2 * Copyright (C) 2011 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 "archivewidget.h"
22 #include "titlewidget.h"
25 #include <KDiskFreeSpaceInfo>
26 #include <KUrlRequester>
27 #include <KFileDialog>
28 #include <KMessageBox>
30 #include <KIO/NetAccess>
33 #include <QTreeWidget>
34 #include "projectsettings.h"
37 ArchiveWidget::ArchiveWidget(QDomDocument doc, QList <DocClipBase*> list, QStringList luma_list, QWidget * parent) :
43 setAttribute(Qt::WA_DeleteOnClose);
45 setWindowTitle(i18n("Archive Project"));
46 archive_url->setUrl(KUrl(QDir::homePath()));
47 connect(archive_url, SIGNAL(textChanged (const QString &)), this, SLOT(slotCheckSpace()));
50 QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
51 videos->setIcon(0, KIcon("video-x-generic"));
52 videos->setData(0, Qt::UserRole, "videos");
53 videos->setExpanded(false);
54 QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
55 sounds->setIcon(0, KIcon("audio-x-generic"));
56 sounds->setData(0, Qt::UserRole, "sounds");
57 sounds->setExpanded(false);
58 QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
59 images->setIcon(0, KIcon("image-x-generic"));
60 images->setData(0, Qt::UserRole, "images");
61 images->setExpanded(false);
62 QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
63 slideshows->setIcon(0, KIcon("image-x-generic"));
64 slideshows->setData(0, Qt::UserRole, "slideshows");
65 slideshows->setExpanded(false);
66 QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
67 texts->setIcon(0, KIcon("text-plain"));
68 texts->setData(0, Qt::UserRole, "texts");
69 texts->setExpanded(false);
70 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
71 others->setIcon(0, KIcon("unknown"));
72 others->setData(0, Qt::UserRole, "others");
73 others->setExpanded(false);
74 QTreeWidgetItem *lumas = new QTreeWidgetItem(files_list, QStringList() << i18n("Luma files"));
75 lumas->setIcon(0, KIcon("image-x-generic"));
76 lumas->setData(0, Qt::UserRole, "lumas");
77 lumas->setExpanded(false);
79 QTreeWidgetItem *proxies = new QTreeWidgetItem(files_list, QStringList() << i18n("Proxy clips"));
80 proxies->setIcon(0, KIcon("video-x-generic"));
81 proxies->setData(0, Qt::UserRole, "proxy");
82 proxies->setExpanded(false);
87 QStringList fileNames;
88 generateItems(lumas, luma_list);
90 QStringList slideUrls;
91 QStringList audioUrls;
92 QStringList videoUrls;
93 QStringList imageUrls;
94 QStringList otherUrls;
95 QStringList proxyUrls;
97 for (int i = 0; i < list.count(); i++) {
98 DocClipBase *clip = list.at(i);
99 CLIPTYPE t = clip->clipType();
100 if (t == SLIDESHOW) {
101 KUrl slideUrl = clip->fileURL();
102 //TODO: Slideshow files
103 slideUrls << slideUrl.path();
105 else if (t == IMAGE) imageUrls << clip->fileURL().path();
106 else if (t == TEXT) {
107 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
108 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
109 imageUrls << imagefiles;
111 } else if (t == PLAYLIST) {
112 QStringList files = ProjectSettings::extractPlaylistUrls(clip->fileURL().path());
115 else if (!clip->fileURL().isEmpty()) {
116 if (t == AUDIO) audioUrls << clip->fileURL().path();
118 videoUrls << clip->fileURL().path();
119 // Check if we have a proxy
120 QString proxy = clip->getProperty("proxy");
121 if (!proxy.isEmpty() && proxy != "-" && QFile::exists(proxy)) proxyUrls << proxy;
126 generateItems(sounds, audioUrls);
127 generateItems(videos, videoUrls);
128 generateItems(images, imageUrls);
129 generateItems(slideshows, slideUrls);
130 generateItems(others, otherUrls);
131 generateItems(proxies, proxyUrls);
133 #if QT_VERSION >= 0x040500
134 allFonts.removeDuplicates();
139 // Hide unused categories, add item count
141 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
142 QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
143 int items = parentItem->childCount();
145 files_list->topLevelItem(i)->setHidden(true);
148 if (parentItem->data(0, Qt::UserRole).toString() == "slideshows")
150 // Special case: slideshows contain several files
151 for (int j = 0; j < items; j++) {
152 total += parentItem->child(j)->data(0, Qt::UserRole + 1).toStringList().count();
156 parentItem->setText(0, files_list->topLevelItem(i)->text(0) + " " + i18np("(%1 item)", "(%1 items)", items));
160 project_files->setText(i18np("%1 file to archive, requires %2", "%1 files to archive, requires %2", total, KIO::convertSize(m_requestedSize)));
161 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
162 connect(buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(slotStartArchiving()));
163 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
168 ArchiveWidget::~ArchiveWidget()
172 void ArchiveWidget::done ( int r )
174 if (closeAccepted()) QDialog::done(r);
177 void ArchiveWidget::closeEvent ( QCloseEvent * e )
180 if (closeAccepted()) e->accept();
185 bool ArchiveWidget::closeAccepted()
187 if (!archive_url->isEnabled()) {
188 // Archiving in progress, should we stop?
189 if (KMessageBox::warningContinueCancel(this, i18n("Archiving in progress, do you want to stop it?"), i18n("Stop Archiving"), KGuiItem(i18n("Stop Archiving"))) != KMessageBox::Continue) {
192 if (m_copyJob) m_copyJob->kill();
198 void ArchiveWidget::generateItems(QTreeWidgetItem *parentItem, QStringList items)
200 QStringList filesList;
203 bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
204 foreach(const QString & file, items) {
205 QTreeWidgetItem *item = new QTreeWidgetItem(parentItem, QStringList() << file);
206 fileName = KUrl(file).fileName();
208 // we store each slideshow in a separate subdirectory
209 item->setData(0, Qt::UserRole, ix);
212 QDir dir(slideUrl.directory(KUrl::AppendTrailingSlash));
213 if (slideUrl.fileName().startsWith(".all.")) {
214 // mimetype slideshow (for example *.png)
217 // TODO: improve jpeg image detection with extension like jpeg, requires change in MLT image producers
218 filters << "*." + slideUrl.fileName().section('.', -1);
219 dir.setNameFilters(filters);
220 QFileInfoList resultList = dir.entryInfoList(QDir::Files);
221 QStringList slideImages;
222 for (int i = 0; i < resultList.count(); i++) {
223 m_requestedSize += resultList.at(i).size();
224 slideImages << resultList.at(i).absoluteFilePath();
226 item->setData(0, Qt::UserRole + 1, slideImages);
229 // pattern url (like clip%.3d.png)
230 QStringList result = dir.entryList(QDir::Files);
231 QString filter = slideUrl.fileName();
232 QString ext = filter.section('.', -1);
233 filter = filter.section('%', 0, -2);
234 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
236 QStringList slideImages;
237 QString directory = dir.absolutePath();
238 if (!directory.endsWith('/')) directory.append('/');
239 foreach(const QString & path, result) {
240 if (rx.exactMatch(path)) {
241 m_requestedSize += QFileInfo(directory + path).size();
242 slideImages << directory + path;
245 item->setData(0, Qt::UserRole + 1, slideImages);
248 else if (filesList.contains(fileName)) {
249 // we have 2 files with same name
251 QString newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
252 while (filesList.contains(newFileName)) {
254 newFileName = fileName.section('.', 0, -2) + "_" + QString::number(ix) + "." + fileName.section('.', -1);
256 fileName = newFileName;
257 item->setData(0, Qt::UserRole, fileName);
260 m_requestedSize += QFileInfo(file).size();
261 filesList << fileName;
266 void ArchiveWidget::slotCheckSpace()
268 KDiskFreeSpaceInfo inf = KDiskFreeSpaceInfo::freeSpaceInfo( archive_url->url().path());
269 KIO::filesize_t freeSize = inf.available();;
270 if (freeSize > m_requestedSize) {
272 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
273 icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
274 text_info->setText(i18n("Available space on drive: %1", KIO::convertSize(freeSize)));
277 buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
278 icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
279 text_info->setText(i18n("Not enough space on drive, free space: %1", KIO::convertSize(freeSize)));
283 bool ArchiveWidget::slotStartArchiving(bool firstPass)
285 if (firstPass && m_copyJob) {
286 // archiving in progress, abort
287 m_copyJob->kill(KJob::EmitResult);
290 if (!firstPass) m_copyJob = NULL;
293 m_duplicateFiles.clear();
294 m_replacementList.clear();
295 archive_url->setEnabled(false);
299 QTreeWidgetItem *parentItem;
300 bool isSlideshow = false;
301 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
302 parentItem = files_list->topLevelItem(i);
303 if (parentItem->childCount() > 0 && !parentItem->isDisabled()) {
304 if (parentItem->data(0, Qt::UserRole).toString() == "slideshows") {
305 KIO::NetAccess::mkdir(KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + "slideshows"), this);
308 files_list->setCurrentItem(parentItem);
309 if (!isSlideshow) parentItem->setDisabled(true);
310 destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + parentItem->data(0, Qt::UserRole).toString());
311 QTreeWidgetItem *item;
312 for (int j = 0; j < parentItem->childCount(); j++) {
313 item = parentItem->child(j);
314 // Special case: slideshows
316 if (item->isDisabled()) {
319 destUrl = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + "/");
320 QStringList srcFiles = item->data(0, Qt::UserRole + 1).toStringList();
321 for (int k = 0; k < srcFiles.count(); k++) {
322 files << KUrl(srcFiles.at(k));
324 item->setDisabled(true);
325 if (parentItem->indexOfChild(item) == parentItem->childCount() - 1) {
326 // We have processed all slideshows
327 parentItem->setDisabled(true);
331 else if (item->data(0, Qt::UserRole).isNull()) {
332 files << KUrl(item->text(0));
335 // We must rename the destination file, since another file with same name exists
336 //TODO: monitor progress
337 m_duplicateFiles.insert(KUrl(item->text(0)), KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString()));
344 if (destUrl.isEmpty()) {
345 if (m_duplicateFiles.isEmpty()) return false;
346 QMapIterator<KUrl, KUrl> i(m_duplicateFiles);
349 KUrl startJobSrc = i.key();
350 KUrl startJobDst = i.value();
351 m_duplicateFiles.remove(startJobSrc);
352 KIO::CopyJob *job = KIO::copyAs(startJobSrc, startJobDst, KIO::HideProgressInfo);
353 connect(job, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
354 connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
359 if (files.isEmpty()) slotStartArchiving(false);
360 KIO::NetAccess::mkdir(destUrl, this);
361 m_copyJob = KIO::copy (files, destUrl, KIO::HideProgressInfo);
362 connect(m_copyJob, SIGNAL(result(KJob *)), this, SLOT(slotArchivingFinished(KJob *)));
363 connect(m_copyJob, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotArchivingProgress(KJob *, qulonglong)));
366 progressBar->setValue(0);
367 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Abort"));
372 void ArchiveWidget::slotArchivingFinished(KJob *job)
374 if (job->error() == 0) {
375 if (slotStartArchiving(false)) {
376 // We still have files to archive
380 // Archiving finished
381 progressBar->setValue(100);
382 if (processProjectFile()) {
383 icon_info->setPixmap(KIcon("dialog-ok").pixmap(16, 16));
384 text_info->setText(i18n("Project was successfully archived."));
387 icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
388 text_info->setText(i18n("There was an error processing project file"));
394 icon_info->setPixmap(KIcon("dialog-close").pixmap(16, 16));
395 text_info->setText(i18n("There was an error while copying the files: %1", job->errorString()));
397 buttonBox->button(QDialogButtonBox::Apply)->setText(i18n("Archive"));
398 archive_url->setEnabled(true);
399 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
400 files_list->topLevelItem(i)->setDisabled(false);
401 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++)
402 files_list->topLevelItem(i)->child(j)->setDisabled(false);
407 void ArchiveWidget::slotArchivingProgress(KJob *, qulonglong size)
409 progressBar->setValue((int) 100 * size / m_requestedSize);
413 bool ArchiveWidget::processProjectFile()
416 QTreeWidgetItem *item;
417 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
418 QTreeWidgetItem *parentItem = files_list->topLevelItem(i);
419 if (parentItem->childCount() > 0) {
420 destUrl = KUrl(archive_url->url().path(KUrl::AddTrailingSlash) + parentItem->data(0, Qt::UserRole).toString());
421 bool isSlideshow = parentItem->data(0, Qt::UserRole).toString() == "slideshows";
422 for (int j = 0; j < parentItem->childCount(); j++) {
423 item = parentItem->child(j);
424 KUrl src(item->text(0));
427 dest = KUrl(destUrl.path(KUrl::AddTrailingSlash) + item->data(0, Qt::UserRole).toString() + "/" + src.fileName());
429 else if (item->data(0, Qt::UserRole).isNull()) {
430 dest.addPath(src.fileName());
433 dest.addPath(item->data(0, Qt::UserRole).toString());
435 m_replacementList.insert(src, dest);
436 kDebug()<<"___ PROCESS ITEM :"<<src << "="<<dest;
441 // process kdenlive producers
442 QDomElement mlt = m_doc.documentElement();
443 QString root = mlt.attribute("root") + "/";
444 mlt.setAttribute("root", archive_url->url().path(KUrl::RemoveTrailingSlash));
445 QDomNodeList prods = mlt.elementsByTagName("kdenlive_producer");
446 for (int i = 0; i < prods.count(); i++) {
447 QDomElement e = prods.item(i).toElement();
448 if (e.isNull()) continue;
449 if (e.hasAttribute("resource")) {
450 KUrl src(e.attribute("resource"));
451 KUrl dest = m_replacementList.value(src);
452 if (!dest.isEmpty()) e.setAttribute("resource", dest.path());
454 if (e.hasAttribute("proxy") && e.attribute("proxy") != "-") {
455 KUrl src(e.attribute("proxy"));
456 KUrl dest = m_replacementList.value(src);
457 if (!dest.isEmpty()) e.setAttribute("proxy", dest.path());
461 // process mlt producers
462 prods = mlt.elementsByTagName("producer");
463 for (int i = 0; i < prods.count(); i++) {
464 QDomElement e = prods.item(i).toElement();
465 if (e.isNull()) continue;
466 QString src = EffectsList::property(e, "resource");
467 if (!src.isEmpty()) {
468 if (!src.startsWith('/')) src.prepend(root);
470 KUrl dest = m_replacementList.value(src);
471 if (!dest.isEmpty()) EffectsList::setProperty(e, "resource", dest.path());
475 // process mlt transitions (for luma files)
476 prods = mlt.elementsByTagName("transition");
478 for (int i = 0; i < prods.count(); i++) {
479 QDomElement e = prods.item(i).toElement();
480 if (e.isNull()) continue;
481 attribute = "resource";
482 QString src = EffectsList::property(e, attribute);
483 if (src.isEmpty()) attribute = "luma";
484 src = EffectsList::property(e, attribute);
485 if (!src.isEmpty()) {
486 if (!src.startsWith('/')) src.prepend(root);
488 KUrl dest = m_replacementList.value(src);
489 if (!dest.isEmpty()) EffectsList::setProperty(e, attribute, dest.path());
493 QString path = archive_url->url().path(KUrl::AddTrailingSlash) + "project.kdenlive";
495 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
496 kWarning() << "////// ERROR writing to file: " << path;
497 KMessageBox::error(this, i18n("Cannot write to file %1", path));
501 file.write(m_doc.toString().toUtf8());
502 if (file.error() != QFile::NoError) {
503 KMessageBox::error(this, i18n("Cannot write to file %1", path));