]> git.sesse.net Git - kdenlive/blob - src/projectsettings.cpp
Required changes to make Kdenlive work with some locales that have a comma (,) as...
[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     if (KMessageBox::warningContinueCancel(this, i18n("Deleting proxy clips will disable proxies for this project.")) != KMessageBox::Continue) return;
191     buttonBox->setEnabled(false);
192     enable_proxy->setChecked(false);
193     emit disableProxies();
194     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
195     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
196     buttonBox->setEnabled(true);
197     slotUpdateFiles(true);
198 }
199
200 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
201 {
202     KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
203     job->exec();
204     thumbs_count->setText(QString::number(job->totalFiles()));
205     thumbs_size->setText(KIO::convertSize(job->totalSize()));
206     job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
207     job->exec();
208     proxy_count->setText(QString::number(job->totalFiles()));
209     proxy_size->setText(KIO::convertSize(job->totalSize()));
210     delete job;
211     if (cacheOnly) return;
212     int unused = 0;
213     int used = 0;
214     KIO::filesize_t usedSize = 0;
215     KIO::filesize_t unUsedSize = 0;
216     QList <DocClipBase*> list = m_projectList->documentClipList();
217     files_list->clear();
218
219     // List all files that are used in the project. That also means:
220     // images included in slideshow and titles, files in playlist clips
221     // TODO: images used in luma transitions, files used for LADSPA effects?
222
223     // Setup categories
224     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
225     videos->setIcon(0, KIcon("video-x-generic"));
226     videos->setExpanded(true);
227     QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
228     sounds->setIcon(0, KIcon("audio-x-generic"));
229     sounds->setExpanded(true);
230     QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
231     images->setIcon(0, KIcon("image-x-generic"));
232     images->setExpanded(true);
233     QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
234     slideshows->setIcon(0, KIcon("image-x-generic"));
235     slideshows->setExpanded(true);
236     QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
237     texts->setIcon(0, KIcon("text-plain"));
238     texts->setExpanded(true);
239     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
240     others->setIcon(0, KIcon("unknown"));
241     others->setExpanded(true);
242     int count = 0;
243     QStringList allFonts;
244     foreach(const QString & file, m_lumas) {
245         count++;
246         new QTreeWidgetItem(images, QStringList() << file);
247     }
248
249     for (int i = 0; i < list.count(); i++) {
250         DocClipBase *clip = list.at(i);
251         if (clip->clipType() == SLIDESHOW) {
252             QStringList subfiles = extractSlideshowUrls(clip->fileURL());
253             foreach(const QString & file, subfiles) {
254                 count++;
255                 new QTreeWidgetItem(slideshows, QStringList() << file);
256             }
257         } else if (!clip->fileURL().isEmpty()) {
258             //allFiles.append(clip->fileURL().path());
259             switch (clip->clipType()) {
260             case TEXT:
261                 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
262                 break;
263             case AUDIO:
264                 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
265                 break;
266             case IMAGE:
267                 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
268                 break;
269             case UNKNOWN:
270                 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
271                 break;
272             default:
273                 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
274                 break;
275             }
276             count++;
277         }
278         if (clip->clipType() == TEXT) {
279             QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
280             QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
281             foreach(const QString & file, imagefiles) {
282                 count++;
283                 new QTreeWidgetItem(images, QStringList() << file);
284             }
285             allFonts << fonts;
286         } else if (clip->clipType() == PLAYLIST) {
287             QStringList files = extractPlaylistUrls(clip->fileURL().path());
288             foreach(const QString & file, files) {
289                 count++;
290                 new QTreeWidgetItem(others, QStringList() << file);
291             }
292         }
293
294         if (clip->numReferences() == 0) {
295             unused++;
296             unUsedSize += clip->fileSize();
297         } else {
298             used++;
299             usedSize += clip->fileSize();
300         }
301     }
302 #if QT_VERSION >= 0x040500
303     allFonts.removeDuplicates();
304 #endif
305     // Hide unused categories
306     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
307         if (files_list->topLevelItem(i)->childCount() == 0) {
308             files_list->topLevelItem(i)->setHidden(true);
309         }
310     }
311     files_count->setText(QString::number(count));
312     fonts_list->addItems(allFonts);
313     if (allFonts.isEmpty()) {
314         fonts_list->setHidden(true);
315         label_fonts->setHidden(true);
316     }
317     used_count->setText(QString::number(used));
318     used_size->setText(KIO::convertSize(usedSize));
319     unused_count->setText(QString::number(unused));
320     unused_size->setText(KIO::convertSize(unUsedSize));
321     delete_unused->setEnabled(unused > 0);
322 }
323
324 void ProjectSettings::accept()
325 {
326     if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
327         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;
328     QDialog::accept();
329 }
330
331 void ProjectSettings::slotUpdateDisplay()
332 {
333     QLocale locale;
334     QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
335     QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
336     p_size->setText(values.value("width") + 'x' + values.value("height"));
337     p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
338     p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
339     p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
340     if (values.value("progressive").toInt() == 0) {
341         p_progressive->setText(i18n("Interlaced (%1 fields per second)",
342                                     locale.toString((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
343     } else {
344         p_progressive->setText(i18n("Progressive"));
345     }
346     p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
347 }
348
349 void ProjectSettings::slotUpdateButton(const QString &path)
350 {
351     if (path.isEmpty()) m_buttonOk->setEnabled(false);
352     else {
353         m_buttonOk->setEnabled(true);
354         slotUpdateFiles(true);
355     }
356 }
357
358 QString ProjectSettings::selectedProfile() const
359 {
360     return profiles_list->itemData(profiles_list->currentIndex()).toString();
361 }
362
363 KUrl ProjectSettings::selectedFolder() const
364 {
365     return project_folder->url();
366 }
367
368 QPoint ProjectSettings::tracks()
369 {
370     QPoint p;
371     p.setX(video_tracks->value());
372     p.setY(audio_tracks->value());
373     return p;
374 }
375
376 bool ProjectSettings::enableVideoThumbs() const
377 {
378     return video_thumbs->isChecked();
379 }
380
381 bool ProjectSettings::enableAudioThumbs() const
382 {
383     return audio_thumbs->isChecked();
384 }
385
386 bool ProjectSettings::useProxy() const
387 {
388     return enable_proxy->isChecked();
389 }
390
391 bool ProjectSettings::generateProxy() const
392 {
393     return generate_proxy->isChecked();
394 }
395
396 bool ProjectSettings::generateImageProxy() const
397 {
398     return generate_imageproxy->isChecked();
399 }
400
401 int ProjectSettings::proxyMinSize() const
402 {
403     return proxy_minsize->value();
404 }
405
406 int ProjectSettings::proxyImageMinSize() const
407 {
408     return proxy_imageminsize->value();
409 }
410
411 QString ProjectSettings::proxyParams() const
412 {
413     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
414     return params.section(';', 0, 0);
415 }
416
417 QString ProjectSettings::proxyExtension() const
418 {
419     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
420     return params.section(';', 1, 1);
421 }
422
423 //static
424 QStringList ProjectSettings::extractPlaylistUrls(QString path)
425 {
426     QStringList urls;
427     QDomDocument doc;
428     QFile file(path);
429     if (!file.open(QIODevice::ReadOnly))
430         return urls;
431     if (!doc.setContent(&file)) {
432         file.close();
433         return urls;
434     }
435     file.close();
436     QString root = doc.documentElement().attribute("root");
437     if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
438     QDomNodeList files = doc.elementsByTagName("producer");
439     for (int i = 0; i < files.count(); i++) {
440         QDomElement e = files.at(i).toElement();
441         QString type = EffectsList::property(e, "mlt_service");
442         if (type != "colour") {
443             QString url = EffectsList::property(e, "resource");
444             if (!url.isEmpty()) {
445                 if (!url.startsWith('/')) url.prepend(root);
446                 if (url.section('.', 0, -2).endsWith("/.all")) {
447                     // slideshow clip, extract image urls
448                     urls << extractSlideshowUrls(KUrl(url));
449                 } else urls << url;
450                 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
451                     //TODO: Do something to avoid infinite loops if 2 files reference themselves...
452                     urls << extractPlaylistUrls(url);
453                 }
454             }
455         }
456     }
457
458     // luma files for transitions
459     files = doc.elementsByTagName("transition");
460     for (int i = 0; i < files.count(); i++) {
461         QDomElement e = files.at(i).toElement();
462         QString url = EffectsList::property(e, "luma");
463         if (!url.isEmpty()) {
464             if (!url.startsWith('/')) url.prepend(root);
465             urls << url;
466         }
467     }
468
469     return urls;
470 }
471
472
473 //static
474 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
475 {
476     QStringList urls;
477     QString path = url.directory(KUrl::AppendTrailingSlash);
478     QString ext = url.path().section('.', -1);
479     QDir dir(path);
480     if (url.path().contains(".all.")) {
481         // this is a mime slideshow, like *.jpeg
482         QStringList filters;
483         filters << "*." + ext;
484         dir.setNameFilters(filters);
485         QStringList result = dir.entryList(QDir::Files);
486         urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
487     } else {
488         // this is a pattern slideshow, like sequence%4d.jpg
489         QString filter = url.fileName();
490         QString ext = filter.section('.', -1);
491         filter = filter.section('%', 0, -2);
492         QString regexp = "^" + filter + "\\d+\\." + ext + "$";
493         QRegExp rx(regexp);
494         int count = 0;
495         QStringList result = dir.entryList(QDir::Files);
496         foreach(const QString & path, result) {
497             if (rx.exactMatch(path)) count++;
498         }
499         urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
500     }
501     return urls;
502 }
503
504 void ProjectSettings::slotExportToText()
505 {
506     QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
507     if (savePath.isEmpty()) return;
508     QString data;
509     data.append(i18n("Project folder: %1",  project_folder->url().path()) + "\n");
510     data.append(i18n("Project profile: %1",  profiles_list->currentText()) + "\n");
511     data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
512     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
513         if (files_list->topLevelItem(i)->childCount() > 0) {
514             data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
515             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
516                 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
517             }
518         }
519     }
520     KTemporaryFile tmpfile;
521     if (!tmpfile.open()) {
522         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
523         return;
524     }
525     QFile xmlf(tmpfile.fileName());
526     xmlf.open(QIODevice::WriteOnly);
527     xmlf.write(data.toUtf8());
528     if (xmlf.error() != QFile::NoError) {
529         xmlf.close();
530         return;
531     }
532     xmlf.close();
533     KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
534 }
535
536 void ProjectSettings::slotUpdateProxyParams()
537 {
538     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
539     proxyparams->setPlainText(params.section(';', 0, 0));
540 }
541
542 #include "projectsettings.moc"
543
544