]> git.sesse.net Git - kdenlive/blob - src/projectsettings.cpp
Merge branch 'buildsystem' into next
[kdenlive] / src / projectsettings.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 #include "projectsettings.h"
21 #include "kdenlivesettings.h"
22 #include "profilesdialog.h"
23 #include "docclipbase.h"
24 #include "titlewidget.h"
25 #include "effectslist.h"
26
27 #include <KStandardDirs>
28 #include <KMessageBox>
29 #include <KDebug>
30 #include <kio/directorysizejob.h>
31 #include <KIO/NetAccess>
32 #include <KTemporaryFile>
33 #include <KFileDialog>
34
35 #include <QDir>
36 #include <kmessagebox.h>
37
38 ProjectSettings::ProjectSettings(ProjectList *projectlist, QStringList lumas, int videotracks, int audiotracks, const QString projectPath, bool readOnlyTracks, bool savedProject, QWidget * parent) :
39     QDialog(parent), m_savedProject(savedProject), m_projectList(projectlist), m_lumas(lumas)
40 {
41     setupUi(this);
42
43     list_search->setTreeWidget(files_list);
44
45     QMap <QString, QString> profilesInfo = ProfilesDialog::getProfilesInfo();
46     QMapIterator<QString, QString> i(profilesInfo);
47     while (i.hasNext()) {
48         i.next();
49         profiles_list->addItem(i.key(), i.value());
50     }
51     project_folder->setMode(KFile::Directory);
52     project_folder->setUrl(KUrl(projectPath));
53     QString currentProf = KdenliveSettings::current_profile();
54
55     for (int i = 0; i < profiles_list->count(); i++) {
56         if (profiles_list->itemData(i).toString() == currentProf) {
57             profiles_list->setCurrentIndex(i);
58             break;
59         }
60     }
61
62     m_buttonOk = buttonBox->button(QDialogButtonBox::Ok);
63     //buttonOk->setEnabled(false);
64     audio_thumbs->setChecked(KdenliveSettings::audiothumbnails());
65     video_thumbs->setChecked(KdenliveSettings::videothumbnails());
66     audio_tracks->setValue(audiotracks);
67     video_tracks->setValue(videotracks);
68     connect(generate_proxy, SIGNAL(toggled(bool)), proxy_minsize, SLOT(setEnabled(bool)));
69     connect(generate_imageproxy, SIGNAL(toggled(bool)), proxy_imageminsize, SLOT(setEnabled(bool)));
70     QString proxyparameters;
71     QString proxyextension;
72     if (projectlist) {
73         enable_proxy->setChecked(projectlist->getDocumentProperty("enableproxy").toInt());
74         generate_proxy->setChecked(projectlist->getDocumentProperty("generateproxy").toInt());
75         proxy_minsize->setValue(projectlist->getDocumentProperty("proxyminsize").toInt());
76         proxyparameters = projectlist->getDocumentProperty("proxyparams");
77         generate_imageproxy->setChecked(projectlist->getDocumentProperty("generateimageproxy").toInt());
78         proxy_imageminsize->setValue(projectlist->getDocumentProperty("proxyimageminsize").toInt());
79         proxyextension = projectlist->getDocumentProperty("proxyextension");
80     }
81     else {
82         enable_proxy->setChecked(KdenliveSettings::enableproxy());
83         generate_proxy->setChecked(KdenliveSettings::generateproxy());
84         proxy_minsize->setValue(KdenliveSettings::proxyminsize());
85         proxyparameters = KdenliveSettings::proxyparams();
86         generate_imageproxy->setChecked(KdenliveSettings::generateimageproxy());
87         proxy_imageminsize->setValue(KdenliveSettings::proxyimageminsize());
88         proxyextension = KdenliveSettings::proxyextension();
89       
90     }
91
92     proxy_minsize->setEnabled(generate_proxy->isChecked());
93     proxy_imageminsize->setEnabled(generate_imageproxy->isChecked());
94
95
96     // load proxy profiles
97     KConfig conf("encodingprofiles.rc", KConfig::FullConfig, "appdata");
98     KConfigGroup group(&conf, "proxy");
99     QMap <QString, QString> values = group.entryMap();
100     QMapIterator<QString, QString> k(values);
101     int ix = -1;
102     while (k.hasNext()) {
103         k.next();
104         if (!k.key().isEmpty()) {
105             QString params = k.value().section(';', 0, 0);
106             QString extension = k.value().section(';', 1, 1);
107             if (ix == -1 && ((params == proxyparameters && extension == proxyextension) || (proxyparameters.isEmpty() || proxyextension.isEmpty()))) {
108                 // this is the current profile
109                 ix = proxy_profile->count();
110             }
111             proxy_profile->addItem(k.key(), k.value());
112         }
113     }
114     if (ix == -1) {
115         // Current project proxy settings not found
116         ix = proxy_profile->count();
117         proxy_profile->addItem(i18n("Current Settings"), QString(proxyparameters + ';' + proxyextension));
118     }
119     proxy_profile->setCurrentIndex(ix);
120     slotUpdateProxyParams();
121
122     // Proxy GUI stuff
123     proxy_showprofileinfo->setIcon(KIcon("help-about"));
124     connect(proxy_profile, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateProxyParams()));
125     proxyparams->setVisible(false);
126     proxyparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
127     connect(proxy_showprofileinfo, SIGNAL(clicked(bool)), proxyparams, SLOT(setVisible(bool)));
128     
129     if (readOnlyTracks) {
130         video_tracks->setEnabled(false);
131         audio_tracks->setEnabled(false);
132     }
133     slotUpdateDisplay();
134     if (m_projectList != NULL) {
135         slotUpdateFiles();
136         connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
137         connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
138         connect(delete_proxies, SIGNAL(clicked()), this, SLOT(slotDeleteProxies()));
139     } else tabWidget->widget(1)->setEnabled(false);
140     connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
141     connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
142     connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
143 }
144
145 void ProjectSettings::slotDeleteUnused()
146 {
147     QStringList toDelete;
148     QList <DocClipBase*> list = m_projectList->documentClipList();
149     for (int i = 0; i < list.count(); i++) {
150         DocClipBase *clip = list.at(i);
151         if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
152             KUrl url = clip->fileURL();
153             if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
154         }
155     }
156
157     // make sure our urls are not used in another clip
158     for (int i = 0; i < list.count(); i++) {
159         DocClipBase *clip = list.at(i);
160         if (clip->numReferences() > 0) {
161             KUrl url = clip->fileURL();
162             if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
163         }
164     }
165
166     if (toDelete.count() == 0) {
167         // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
168         if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
169         m_projectList->cleanup();
170         slotUpdateFiles();
171         return;
172     }
173     if (KMessageBox::warningYesNoList(this, i18n("This will remove the following files from your hard drive.\nThis action cannot be undone, only use if you know what you are doing.\nAre you sure you want to continue?"), toDelete, i18n("Delete unused clips")) != KMessageBox::Yes) return;
174     m_projectList->trashUnusedClips();
175     slotUpdateFiles();
176 }
177
178 void ProjectSettings::slotClearCache()
179 {
180     buttonBox->setEnabled(false);
181     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
182     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
183     buttonBox->setEnabled(true);
184     slotUpdateFiles(true);
185 }
186
187 void ProjectSettings::slotDeleteProxies()
188 {
189     if (KMessageBox::warningContinueCancel(this, i18n("Deleting proxy clips will disable proxies for this project.")) != KMessageBox::Continue) return;
190     buttonBox->setEnabled(false);
191     enable_proxy->setChecked(false);
192     emit disableProxies();
193     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
194     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
195     buttonBox->setEnabled(true);
196     slotUpdateFiles(true);
197 }
198
199 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
200 {
201     KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
202     job->exec();
203     thumbs_count->setText(QString::number(job->totalFiles()));
204     thumbs_size->setText(KIO::convertSize(job->totalSize()));
205     job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
206     job->exec();
207     proxy_count->setText(QString::number(job->totalFiles()));
208     proxy_size->setText(KIO::convertSize(job->totalSize()));
209     delete job;
210     if (cacheOnly) return;
211     int unused = 0;
212     int used = 0;
213     KIO::filesize_t usedSize = 0;
214     KIO::filesize_t unUsedSize = 0;
215     QList <DocClipBase*> list = m_projectList->documentClipList();
216     files_list->clear();
217
218     // List all files that are used in the project. That also means:
219     // images included in slideshow and titles, files in playlist clips
220     // TODO: images used in luma transitions?
221
222     // Setup categories
223     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
224     videos->setIcon(0, KIcon("video-x-generic"));
225     videos->setExpanded(true);
226     QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
227     sounds->setIcon(0, KIcon("audio-x-generic"));
228     sounds->setExpanded(true);
229     QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
230     images->setIcon(0, KIcon("image-x-generic"));
231     images->setExpanded(true);
232     QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
233     slideshows->setIcon(0, KIcon("image-x-generic"));
234     slideshows->setExpanded(true);
235     QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
236     texts->setIcon(0, KIcon("text-plain"));
237     texts->setExpanded(true);
238     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
239     others->setIcon(0, KIcon("unknown"));
240     others->setExpanded(true);
241     int count = 0;
242     QStringList allFonts;
243     foreach(const QString & file, m_lumas) {
244         count++;
245         new QTreeWidgetItem(images, QStringList() << file);
246     }
247
248     for (int i = 0; i < list.count(); i++) {
249         DocClipBase *clip = list.at(i);
250         if (clip->clipType() == SLIDESHOW) {
251             QStringList subfiles = extractSlideshowUrls(clip->fileURL());
252             foreach(const QString & file, subfiles) {
253                 count++;
254                 new QTreeWidgetItem(slideshows, QStringList() << file);
255             }
256         } else if (!clip->fileURL().isEmpty()) {
257             //allFiles.append(clip->fileURL().path());
258             switch (clip->clipType()) {
259             case TEXT:
260                 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
261                 break;
262             case AUDIO:
263                 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
264                 break;
265             case IMAGE:
266                 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
267                 break;
268             case UNKNOWN:
269                 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
270                 break;
271             default:
272                 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
273                 break;
274             }
275             count++;
276         }
277         if (clip->clipType() == TEXT) {
278             QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
279             QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
280             foreach(const QString & file, imagefiles) {
281                 count++;
282                 new QTreeWidgetItem(images, QStringList() << file);
283             }
284             allFonts << fonts;
285         } else if (clip->clipType() == PLAYLIST) {
286             QStringList files = extractPlaylistUrls(clip->fileURL().path());
287             foreach(const QString & file, files) {
288                 count++;
289                 new QTreeWidgetItem(others, QStringList() << file);
290             }
291         }
292
293         if (clip->numReferences() == 0) {
294             unused++;
295             unUsedSize += clip->fileSize();
296         } else {
297             used++;
298             usedSize += clip->fileSize();
299         }
300     }
301     allFonts.removeDuplicates();
302     // Hide unused categories
303     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
304         if (files_list->topLevelItem(i)->childCount() == 0) {
305             files_list->topLevelItem(i)->setHidden(true);
306         }
307     }
308     files_count->setText(QString::number(count));
309     fonts_list->addItems(allFonts);
310     if (allFonts.isEmpty()) {
311         fonts_list->setHidden(true);
312         label_fonts->setHidden(true);
313     }
314     used_count->setText(QString::number(used));
315     used_size->setText(KIO::convertSize(usedSize));
316     unused_count->setText(QString::number(unused));
317     unused_size->setText(KIO::convertSize(unUsedSize));
318     delete_unused->setEnabled(unused > 0);
319 }
320
321 void ProjectSettings::accept()
322 {
323     if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
324         if (KMessageBox::warningContinueCancel(this, i18n("Changing the profile of your project cannot be undone.\nIt is recommended to save your project before attempting this operation that might cause some corruption in transitions.\n Are you sure you want to proceed?"), i18n("Confirm profile change")) == KMessageBox::Cancel) return;
325     QDialog::accept();
326 }
327
328 void ProjectSettings::slotUpdateDisplay()
329 {
330     QLocale locale;
331     QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
332     QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
333     p_size->setText(values.value("width") + 'x' + values.value("height"));
334     p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
335     p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
336     p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
337     if (values.value("progressive").toInt() == 0) {
338         p_progressive->setText(i18n("Interlaced (%1 fields per second)",
339                                     locale.toString((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
340     } else {
341         p_progressive->setText(i18n("Progressive"));
342     }
343     p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
344 }
345
346 void ProjectSettings::slotUpdateButton(const QString &path)
347 {
348     if (path.isEmpty()) m_buttonOk->setEnabled(false);
349     else {
350         m_buttonOk->setEnabled(true);
351         slotUpdateFiles(true);
352     }
353 }
354
355 QString ProjectSettings::selectedProfile() const
356 {
357     return profiles_list->itemData(profiles_list->currentIndex()).toString();
358 }
359
360 KUrl ProjectSettings::selectedFolder() const
361 {
362     return project_folder->url();
363 }
364
365 QPoint ProjectSettings::tracks()
366 {
367     QPoint p;
368     p.setX(video_tracks->value());
369     p.setY(audio_tracks->value());
370     return p;
371 }
372
373 bool ProjectSettings::enableVideoThumbs() const
374 {
375     return video_thumbs->isChecked();
376 }
377
378 bool ProjectSettings::enableAudioThumbs() const
379 {
380     return audio_thumbs->isChecked();
381 }
382
383 bool ProjectSettings::useProxy() const
384 {
385     return enable_proxy->isChecked();
386 }
387
388 bool ProjectSettings::generateProxy() const
389 {
390     return generate_proxy->isChecked();
391 }
392
393 bool ProjectSettings::generateImageProxy() const
394 {
395     return generate_imageproxy->isChecked();
396 }
397
398 int ProjectSettings::proxyMinSize() const
399 {
400     return proxy_minsize->value();
401 }
402
403 int ProjectSettings::proxyImageMinSize() const
404 {
405     return proxy_imageminsize->value();
406 }
407
408 QString ProjectSettings::proxyParams() const
409 {
410     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
411     return params.section(';', 0, 0);
412 }
413
414 QString ProjectSettings::proxyExtension() const
415 {
416     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
417     return params.section(';', 1, 1);
418 }
419
420 //static
421 QStringList ProjectSettings::extractPlaylistUrls(QString path)
422 {
423     QStringList urls;
424     QDomDocument doc;
425     QFile file(path);
426     if (!file.open(QIODevice::ReadOnly))
427         return urls;
428     if (!doc.setContent(&file)) {
429         file.close();
430         return urls;
431     }
432     file.close();
433     QString root = doc.documentElement().attribute("root");
434     if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
435     QDomNodeList files = doc.elementsByTagName("producer");
436     for (int i = 0; i < files.count(); i++) {
437         QDomElement e = files.at(i).toElement();
438         QString type = EffectsList::property(e, "mlt_service");
439         if (type != "colour") {
440             QString url = EffectsList::property(e, "resource");
441             if (!url.isEmpty()) {
442                 if (!url.startsWith('/')) url.prepend(root);
443                 if (url.section('.', 0, -2).endsWith("/.all")) {
444                     // slideshow clip, extract image urls
445                     urls << extractSlideshowUrls(KUrl(url));
446                 } else urls << url;
447                 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
448                     //TODO: Do something to avoid infinite loops if 2 files reference themselves...
449                     urls << extractPlaylistUrls(url);
450                 }
451             }
452         }
453     }
454
455     // luma files for transitions
456     files = doc.elementsByTagName("transition");
457     for (int i = 0; i < files.count(); i++) {
458         QDomElement e = files.at(i).toElement();
459         QString url = EffectsList::property(e, "luma");
460         if (!url.isEmpty()) {
461             if (!url.startsWith('/')) url.prepend(root);
462             urls << url;
463         }
464     }
465
466     return urls;
467 }
468
469
470 //static
471 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
472 {
473     QStringList urls;
474     QString path = url.directory(KUrl::AppendTrailingSlash);
475     QString ext = url.path().section('.', -1);
476     QDir dir(path);
477     if (url.path().contains(".all.")) {
478         // this is a mime slideshow, like *.jpeg
479         QStringList filters;
480         filters << "*." + ext;
481         dir.setNameFilters(filters);
482         QStringList result = dir.entryList(QDir::Files);
483         urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
484     } else {
485         // this is a pattern slideshow, like sequence%4d.jpg
486         QString filter = url.fileName();
487         QString ext = filter.section('.', -1);
488         filter = filter.section('%', 0, -2);
489         QString regexp = "^" + filter + "\\d+\\." + ext + "$";
490         QRegExp rx(regexp);
491         int count = 0;
492         QStringList result = dir.entryList(QDir::Files);
493         foreach(const QString & path, result) {
494             if (rx.exactMatch(path)) count++;
495         }
496         urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
497     }
498     return urls;
499 }
500
501 void ProjectSettings::slotExportToText()
502 {
503     QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
504     if (savePath.isEmpty()) return;
505     QString data;
506     data.append(i18n("Project folder: %1",  project_folder->url().path()) + "\n");
507     data.append(i18n("Project profile: %1",  profiles_list->currentText()) + "\n");
508     data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
509     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
510         if (files_list->topLevelItem(i)->childCount() > 0) {
511             data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
512             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
513                 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
514             }
515         }
516     }
517     KTemporaryFile tmpfile;
518     if (!tmpfile.open()) {
519         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
520         return;
521     }
522     QFile xmlf(tmpfile.fileName());
523     xmlf.open(QIODevice::WriteOnly);
524     xmlf.write(data.toUtf8());
525     if (xmlf.error() != QFile::NoError) {
526         xmlf.close();
527         return;
528     }
529     xmlf.close();
530     KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
531 }
532
533 void ProjectSettings::slotUpdateProxyParams()
534 {
535     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
536     proxyparams->setPlainText(params.section(';', 0, 0));
537 }
538
539 #include "projectsettings.moc"
540
541