]> git.sesse.net Git - kdenlive/blob - src/projectsettings.cpp
Fix proxy settings sometimes broken (missing extension)
[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 #if QT_VERSION >= 0x040500
302     allFonts.removeDuplicates();
303 #endif
304     // Hide unused categories
305     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
306         if (files_list->topLevelItem(i)->childCount() == 0) {
307             files_list->topLevelItem(i)->setHidden(true);
308         }
309     }
310     files_count->setText(QString::number(count));
311     fonts_list->addItems(allFonts);
312     if (allFonts.isEmpty()) {
313         fonts_list->setHidden(true);
314         label_fonts->setHidden(true);
315     }
316     used_count->setText(QString::number(used));
317     used_size->setText(KIO::convertSize(usedSize));
318     unused_count->setText(QString::number(unused));
319     unused_size->setText(KIO::convertSize(unUsedSize));
320     delete_unused->setEnabled(unused > 0);
321 }
322
323 void ProjectSettings::accept()
324 {
325     if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
326         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;
327     QDialog::accept();
328 }
329
330 void ProjectSettings::slotUpdateDisplay()
331 {
332     QLocale locale;
333     QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
334     QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
335     p_size->setText(values.value("width") + 'x' + values.value("height"));
336     p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
337     p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
338     p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
339     if (values.value("progressive").toInt() == 0) {
340         p_progressive->setText(i18n("Interlaced (%1 fields per second)",
341                                     locale.toString((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
342     } else {
343         p_progressive->setText(i18n("Progressive"));
344     }
345     p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
346 }
347
348 void ProjectSettings::slotUpdateButton(const QString &path)
349 {
350     if (path.isEmpty()) m_buttonOk->setEnabled(false);
351     else {
352         m_buttonOk->setEnabled(true);
353         slotUpdateFiles(true);
354     }
355 }
356
357 QString ProjectSettings::selectedProfile() const
358 {
359     return profiles_list->itemData(profiles_list->currentIndex()).toString();
360 }
361
362 KUrl ProjectSettings::selectedFolder() const
363 {
364     return project_folder->url();
365 }
366
367 QPoint ProjectSettings::tracks()
368 {
369     QPoint p;
370     p.setX(video_tracks->value());
371     p.setY(audio_tracks->value());
372     return p;
373 }
374
375 bool ProjectSettings::enableVideoThumbs() const
376 {
377     return video_thumbs->isChecked();
378 }
379
380 bool ProjectSettings::enableAudioThumbs() const
381 {
382     return audio_thumbs->isChecked();
383 }
384
385 bool ProjectSettings::useProxy() const
386 {
387     return enable_proxy->isChecked();
388 }
389
390 bool ProjectSettings::generateProxy() const
391 {
392     return generate_proxy->isChecked();
393 }
394
395 bool ProjectSettings::generateImageProxy() const
396 {
397     return generate_imageproxy->isChecked();
398 }
399
400 int ProjectSettings::proxyMinSize() const
401 {
402     return proxy_minsize->value();
403 }
404
405 int ProjectSettings::proxyImageMinSize() const
406 {
407     return proxy_imageminsize->value();
408 }
409
410 QString ProjectSettings::proxyParams() const
411 {
412     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
413     return params.section(';', 0, 0);
414 }
415
416 QString ProjectSettings::proxyExtension() const
417 {
418     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
419     return params.section(';', 1, 1);
420 }
421
422 //static
423 QStringList ProjectSettings::extractPlaylistUrls(QString path)
424 {
425     QStringList urls;
426     QDomDocument doc;
427     QFile file(path);
428     if (!file.open(QIODevice::ReadOnly))
429         return urls;
430     if (!doc.setContent(&file)) {
431         file.close();
432         return urls;
433     }
434     file.close();
435     QString root = doc.documentElement().attribute("root");
436     if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
437     QDomNodeList files = doc.elementsByTagName("producer");
438     for (int i = 0; i < files.count(); i++) {
439         QDomElement e = files.at(i).toElement();
440         QString type = EffectsList::property(e, "mlt_service");
441         if (type != "colour") {
442             QString url = EffectsList::property(e, "resource");
443             if (!url.isEmpty()) {
444                 if (!url.startsWith('/')) url.prepend(root);
445                 if (url.section('.', 0, -2).endsWith("/.all")) {
446                     // slideshow clip, extract image urls
447                     urls << extractSlideshowUrls(KUrl(url));
448                 } else urls << url;
449                 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
450                     //TODO: Do something to avoid infinite loops if 2 files reference themselves...
451                     urls << extractPlaylistUrls(url);
452                 }
453             }
454         }
455     }
456
457     // luma files for transitions
458     files = doc.elementsByTagName("transition");
459     for (int i = 0; i < files.count(); i++) {
460         QDomElement e = files.at(i).toElement();
461         QString url = EffectsList::property(e, "luma");
462         if (!url.isEmpty()) {
463             if (!url.startsWith('/')) url.prepend(root);
464             urls << url;
465         }
466     }
467
468     return urls;
469 }
470
471
472 //static
473 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
474 {
475     QStringList urls;
476     QString path = url.directory(KUrl::AppendTrailingSlash);
477     QString ext = url.path().section('.', -1);
478     QDir dir(path);
479     if (url.path().contains(".all.")) {
480         // this is a mime slideshow, like *.jpeg
481         QStringList filters;
482         filters << "*." + ext;
483         dir.setNameFilters(filters);
484         QStringList result = dir.entryList(QDir::Files);
485         urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
486     } else {
487         // this is a pattern slideshow, like sequence%4d.jpg
488         QString filter = url.fileName();
489         QString ext = filter.section('.', -1);
490         filter = filter.section('%', 0, -2);
491         QString regexp = "^" + filter + "\\d+\\." + ext + "$";
492         QRegExp rx(regexp);
493         int count = 0;
494         QStringList result = dir.entryList(QDir::Files);
495         foreach(const QString & path, result) {
496             if (rx.exactMatch(path)) count++;
497         }
498         urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
499     }
500     return urls;
501 }
502
503 void ProjectSettings::slotExportToText()
504 {
505     QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
506     if (savePath.isEmpty()) return;
507     QString data;
508     data.append(i18n("Project folder: %1",  project_folder->url().path()) + "\n");
509     data.append(i18n("Project profile: %1",  profiles_list->currentText()) + "\n");
510     data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
511     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
512         if (files_list->topLevelItem(i)->childCount() > 0) {
513             data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
514             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
515                 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
516             }
517         }
518     }
519     KTemporaryFile tmpfile;
520     if (!tmpfile.open()) {
521         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
522         return;
523     }
524     QFile xmlf(tmpfile.fileName());
525     xmlf.open(QIODevice::WriteOnly);
526     xmlf.write(data.toUtf8());
527     if (xmlf.error() != QFile::NoError) {
528         xmlf.close();
529         return;
530     }
531     xmlf.close();
532     KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
533 }
534
535 void ProjectSettings::slotUpdateProxyParams()
536 {
537     QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
538     proxyparams->setPlainText(params.section(';', 0, 0));
539 }
540
541 #include "projectsettings.moc"
542
543