]> git.sesse.net Git - kdenlive/blob - src/projectsettings.cpp
41deae0fc5c6976c2d68c703e42a3178a854d220
[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     QString profileFile = KStandardDirs::locateLocal("appdata", "encodingprofiles.rc");
98     KConfig conf(profileFile, KConfig::SimpleConfig);
99     KConfigGroup group(&conf, "proxy");
100     QMap <QString, QString> values = group.entryMap();
101     QMapIterator<QString, QString> k(values);
102     int ix = -1;
103     while (k.hasNext()) {
104         k.next();
105         if (!k.key().isEmpty()) {
106             QString params = k.value().section(';', 0, 0);
107             QString extension = k.value().section(';', 1, 1);
108             if (params == proxyparameters && extension == proxyextension) {
109                 // this is the current profile
110                 ix = proxy_profile->count();
111             }
112             proxy_profile->addItem(k.key(), k.value());
113         }
114     }
115     if (ix == -1) {
116         // Current project proxy settings not found
117         ix = proxy_profile->count();
118         proxy_profile->addItem(i18n("Current Settings"), QString(proxyparameters + ';' + proxyextension));
119     }
120     proxy_profile->setCurrentIndex(ix);
121     slotUpdateProxyParams();
122
123     // Proxy GUI stuff
124     proxy_showprofileinfo->setIcon(KIcon("help-about"));
125     connect(proxy_profile, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateProxyParams()));
126     proxyparams->setVisible(false);
127     proxyparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
128     connect(proxy_showprofileinfo, SIGNAL(clicked(bool)), proxyparams, SLOT(setVisible(bool)));
129     
130     if (readOnlyTracks) {
131         video_tracks->setEnabled(false);
132         audio_tracks->setEnabled(false);
133     }
134     slotUpdateDisplay();
135     if (m_projectList != NULL) {
136         slotUpdateFiles();
137         connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
138         connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
139         connect(delete_proxies, SIGNAL(clicked()), this, SLOT(slotDeleteProxies()));
140     } else tabWidget->widget(1)->setEnabled(false);
141     connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
142     connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
143     connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
144 }
145
146 void ProjectSettings::slotDeleteUnused()
147 {
148     QStringList toDelete;
149     QList <DocClipBase*> list = m_projectList->documentClipList();
150     for (int i = 0; i < list.count(); i++) {
151         DocClipBase *clip = list.at(i);
152         if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
153             KUrl url = clip->fileURL();
154             if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
155         }
156     }
157
158     // make sure our urls are not used in another clip
159     for (int i = 0; i < list.count(); i++) {
160         DocClipBase *clip = list.at(i);
161         if (clip->numReferences() > 0) {
162             KUrl url = clip->fileURL();
163             if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
164         }
165     }
166
167     if (toDelete.count() == 0) {
168         // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
169         if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
170         m_projectList->cleanup();
171         slotUpdateFiles();
172         return;
173     }
174     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;
175     m_projectList->trashUnusedClips();
176     slotUpdateFiles();
177 }
178
179 void ProjectSettings::slotClearCache()
180 {
181     buttonBox->setEnabled(false);
182     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
183     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
184     buttonBox->setEnabled(true);
185     slotUpdateFiles(true);
186 }
187
188 void ProjectSettings::slotDeleteProxies()
189 {
190     buttonBox->setEnabled(false);
191     
192     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
193     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
194     buttonBox->setEnabled(true);
195     slotUpdateFiles(true);
196 }
197
198 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
199 {
200     KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
201     job->exec();
202     thumbs_count->setText(QString::number(job->totalFiles()));
203     thumbs_size->setText(KIO::convertSize(job->totalSize()));
204     job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
205     job->exec();
206     proxy_count->setText(QString::number(job->totalFiles()));
207     proxy_size->setText(KIO::convertSize(job->totalSize()));
208     delete job;
209     if (cacheOnly) return;
210     int unused = 0;
211     int used = 0;
212     KIO::filesize_t usedSize = 0;
213     KIO::filesize_t unUsedSize = 0;
214     QList <DocClipBase*> list = m_projectList->documentClipList();
215     files_list->clear();
216
217     // List all files that are used in the project. That also means:
218     // images included in slideshow and titles, files in playlist clips
219     // TODO: images used in luma transitions, files used for LADSPA effects?
220
221     // Setup categories
222     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
223     videos->setIcon(0, KIcon("video-x-generic"));
224     videos->setExpanded(true);
225     QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
226     sounds->setIcon(0, KIcon("audio-x-generic"));
227     sounds->setExpanded(true);
228     QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
229     images->setIcon(0, KIcon("image-x-generic"));
230     images->setExpanded(true);
231     QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
232     slideshows->setIcon(0, KIcon("image-x-generic"));
233     slideshows->setExpanded(true);
234     QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
235     texts->setIcon(0, KIcon("text-plain"));
236     texts->setExpanded(true);
237     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
238     others->setIcon(0, KIcon("unknown"));
239     others->setExpanded(true);
240     int count = 0;
241     QStringList allFonts;
242     foreach(const QString & file, m_lumas) {
243         count++;
244         new QTreeWidgetItem(images, QStringList() << file);
245     }
246
247     for (int i = 0; i < list.count(); i++) {
248         DocClipBase *clip = list.at(i);
249         if (clip->clipType() == SLIDESHOW) {
250             QStringList subfiles = extractSlideshowUrls(clip->fileURL());
251             foreach(const QString & file, subfiles) {
252                 count++;
253                 new QTreeWidgetItem(slideshows, QStringList() << file);
254             }
255         } else if (!clip->fileURL().isEmpty()) {
256             //allFiles.append(clip->fileURL().path());
257             switch (clip->clipType()) {
258             case TEXT:
259                 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
260                 break;
261             case AUDIO:
262                 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
263                 break;
264             case IMAGE:
265                 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
266                 break;
267             case UNKNOWN:
268                 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
269                 break;
270             default:
271                 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
272                 break;
273             }
274             count++;
275         }
276         if (clip->clipType() == TEXT) {
277             QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
278             QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
279             foreach(const QString & file, imagefiles) {
280                 count++;
281                 new QTreeWidgetItem(images, QStringList() << file);
282             }
283             allFonts << fonts;
284         } else if (clip->clipType() == PLAYLIST) {
285             QStringList files = extractPlaylistUrls(clip->fileURL().path());
286             foreach(const QString & file, files) {
287                 count++;
288                 new QTreeWidgetItem(others, QStringList() << file);
289             }
290         }
291
292         if (clip->numReferences() == 0) {
293             unused++;
294             unUsedSize += clip->fileSize();
295         } else {
296             used++;
297             usedSize += clip->fileSize();
298         }
299     }
300 #if QT_VERSION >= 0x040500
301     allFonts.removeDuplicates();
302 #endif
303     // Hide unused categories
304     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
305         if (files_list->topLevelItem(i)->childCount() == 0) {
306             files_list->topLevelItem(i)->setHidden(true);
307         }
308     }
309     files_count->setText(QString::number(count));
310     fonts_list->addItems(allFonts);
311     if (allFonts.isEmpty()) {
312         fonts_list->setHidden(true);
313         label_fonts->setHidden(true);
314     }
315     used_count->setText(QString::number(used));
316     used_size->setText(KIO::convertSize(usedSize));
317     unused_count->setText(QString::number(unused));
318     unused_size->setText(KIO::convertSize(unUsedSize));
319     delete_unused->setEnabled(unused > 0);
320 }
321
322 void ProjectSettings::accept()
323 {
324     if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
325         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;
326     QDialog::accept();
327 }
328
329 void ProjectSettings::slotUpdateDisplay()
330 {
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                                     QString::number((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