]> git.sesse.net Git - kdenlive/blob - src/projectsettings.cpp
Image clips can be proxied too now
[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     proxy_params->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
69     connect(generate_proxy, SIGNAL(toggled(bool)), proxy_minsize, SLOT(setEnabled(bool)));
70     connect(generate_imageproxy, SIGNAL(toggled(bool)), proxy_imageminsize, SLOT(setEnabled(bool)));
71     
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         proxy_params->setPlainText(projectlist->getDocumentProperty("proxyparams"));
77         generate_imageproxy->setChecked(projectlist->getDocumentProperty("generateimageproxy").toInt());
78         proxy_imageminsize->setValue(projectlist->getDocumentProperty("proxyimageminsize").toInt());
79         proxy_extension->setText(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         proxy_params->setPlainText(KdenliveSettings::proxyparams());
86         generate_imageproxy->setChecked(KdenliveSettings::generateimageproxy());
87         proxy_imageminsize->setValue(KdenliveSettings::proxyimageminsize());
88         proxy_extension->setText(KdenliveSettings::proxyextension());
89       
90     }
91     
92     if (readOnlyTracks) {
93         video_tracks->setEnabled(false);
94         audio_tracks->setEnabled(false);
95     }
96     slotUpdateDisplay();
97     if (m_projectList != NULL) {
98         slotUpdateFiles();
99         connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
100         connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
101         connect(delete_proxies, SIGNAL(clicked()), this, SLOT(slotDeleteProxies()));
102     } else tabWidget->widget(1)->setEnabled(false);
103     connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
104     connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
105     connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
106 }
107
108 void ProjectSettings::slotDeleteUnused()
109 {
110     QStringList toDelete;
111     QList <DocClipBase*> list = m_projectList->documentClipList();
112     for (int i = 0; i < list.count(); i++) {
113         DocClipBase *clip = list.at(i);
114         if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
115             KUrl url = clip->fileURL();
116             if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
117         }
118     }
119
120     // make sure our urls are not used in another clip
121     for (int i = 0; i < list.count(); i++) {
122         DocClipBase *clip = list.at(i);
123         if (clip->numReferences() > 0) {
124             KUrl url = clip->fileURL();
125             if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
126         }
127     }
128
129     if (toDelete.count() == 0) {
130         // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
131         if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
132         m_projectList->cleanup();
133         slotUpdateFiles();
134         return;
135     }
136     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;
137     m_projectList->trashUnusedClips();
138     slotUpdateFiles();
139 }
140
141 void ProjectSettings::slotClearCache()
142 {
143     buttonBox->setEnabled(false);
144     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
145     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
146     buttonBox->setEnabled(true);
147     slotUpdateFiles(true);
148 }
149
150 void ProjectSettings::slotDeleteProxies()
151 {
152     buttonBox->setEnabled(false);
153     
154     KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
155     KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
156     buttonBox->setEnabled(true);
157     slotUpdateFiles(true);
158 }
159
160 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
161 {
162     KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
163     job->exec();
164     thumbs_count->setText(QString::number(job->totalFiles()));
165     thumbs_size->setText(KIO::convertSize(job->totalSize()));
166     job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
167     job->exec();
168     proxy_count->setText(QString::number(job->totalFiles()));
169     proxy_size->setText(KIO::convertSize(job->totalSize()));
170     delete job;
171     if (cacheOnly) return;
172     int unused = 0;
173     int used = 0;
174     KIO::filesize_t usedSize = 0;
175     KIO::filesize_t unUsedSize = 0;
176     QList <DocClipBase*> list = m_projectList->documentClipList();
177     files_list->clear();
178
179     // List all files that are used in the project. That also means:
180     // images included in slideshow and titles, files in playlist clips
181     // TODO: images used in luma transitions, files used for LADSPA effects?
182
183     // Setup categories
184     QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
185     videos->setIcon(0, KIcon("video-x-generic"));
186     videos->setExpanded(true);
187     QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
188     sounds->setIcon(0, KIcon("audio-x-generic"));
189     sounds->setExpanded(true);
190     QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
191     images->setIcon(0, KIcon("image-x-generic"));
192     images->setExpanded(true);
193     QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
194     slideshows->setIcon(0, KIcon("image-x-generic"));
195     slideshows->setExpanded(true);
196     QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
197     texts->setIcon(0, KIcon("text-plain"));
198     texts->setExpanded(true);
199     QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
200     others->setIcon(0, KIcon("unknown"));
201     others->setExpanded(true);
202     int count = 0;
203     QStringList allFonts;
204     foreach(const QString & file, m_lumas) {
205         count++;
206         new QTreeWidgetItem(images, QStringList() << file);
207     }
208
209     for (int i = 0; i < list.count(); i++) {
210         DocClipBase *clip = list.at(i);
211         if (clip->clipType() == SLIDESHOW) {
212             QStringList subfiles = extractSlideshowUrls(clip->fileURL());
213             foreach(const QString & file, subfiles) {
214                 count++;
215                 new QTreeWidgetItem(slideshows, QStringList() << file);
216             }
217         } else if (!clip->fileURL().isEmpty()) {
218             //allFiles.append(clip->fileURL().path());
219             switch (clip->clipType()) {
220             case TEXT:
221                 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
222                 break;
223             case AUDIO:
224                 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
225                 break;
226             case IMAGE:
227                 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
228                 break;
229             case UNKNOWN:
230                 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
231                 break;
232             default:
233                 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
234                 break;
235             }
236             count++;
237         }
238         if (clip->clipType() == TEXT) {
239             QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
240             QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
241             foreach(const QString & file, imagefiles) {
242                 count++;
243                 new QTreeWidgetItem(images, QStringList() << file);
244             }
245             allFonts << fonts;
246         } else if (clip->clipType() == PLAYLIST) {
247             QStringList files = extractPlaylistUrls(clip->fileURL().path());
248             foreach(const QString & file, files) {
249                 count++;
250                 new QTreeWidgetItem(others, QStringList() << file);
251             }
252         }
253
254         if (clip->numReferences() == 0) {
255             unused++;
256             unUsedSize += clip->fileSize();
257         } else {
258             used++;
259             usedSize += clip->fileSize();
260         }
261     }
262 #if QT_VERSION >= 0x040500
263     allFonts.removeDuplicates();
264 #endif
265     // Hide unused categories
266     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
267         if (files_list->topLevelItem(i)->childCount() == 0) {
268             files_list->topLevelItem(i)->setHidden(true);
269         }
270     }
271     files_count->setText(QString::number(count));
272     fonts_list->addItems(allFonts);
273     if (allFonts.isEmpty()) {
274         fonts_list->setHidden(true);
275         label_fonts->setHidden(true);
276     }
277     used_count->setText(QString::number(used));
278     used_size->setText(KIO::convertSize(usedSize));
279     unused_count->setText(QString::number(unused));
280     unused_size->setText(KIO::convertSize(unUsedSize));
281     delete_unused->setEnabled(unused > 0);
282 }
283
284 void ProjectSettings::accept()
285 {
286     if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
287         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;
288     QDialog::accept();
289 }
290
291 void ProjectSettings::slotUpdateDisplay()
292 {
293     QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
294     QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
295     p_size->setText(values.value("width") + 'x' + values.value("height"));
296     p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
297     p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
298     p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
299     if (values.value("progressive").toInt() == 0) {
300         p_progressive->setText(i18n("Interlaced (%1 fields per second)",
301                                     QString::number((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
302     } else {
303         p_progressive->setText(i18n("Progressive"));
304     }
305     p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
306 }
307
308 void ProjectSettings::slotUpdateButton(const QString &path)
309 {
310     if (path.isEmpty()) m_buttonOk->setEnabled(false);
311     else {
312         m_buttonOk->setEnabled(true);
313         slotUpdateFiles(true);
314     }
315 }
316
317 QString ProjectSettings::selectedProfile() const
318 {
319     return profiles_list->itemData(profiles_list->currentIndex()).toString();
320 }
321
322 KUrl ProjectSettings::selectedFolder() const
323 {
324     return project_folder->url();
325 }
326
327 QPoint ProjectSettings::tracks()
328 {
329     QPoint p;
330     p.setX(video_tracks->value());
331     p.setY(audio_tracks->value());
332     return p;
333 }
334
335 bool ProjectSettings::enableVideoThumbs() const
336 {
337     return video_thumbs->isChecked();
338 }
339
340 bool ProjectSettings::enableAudioThumbs() const
341 {
342     return audio_thumbs->isChecked();
343 }
344
345 bool ProjectSettings::useProxy() const
346 {
347     return enable_proxy->isChecked();
348 }
349
350 bool ProjectSettings::generateProxy() const
351 {
352     return generate_proxy->isChecked();
353 }
354
355 bool ProjectSettings::generateImageProxy() const
356 {
357     return generate_imageproxy->isChecked();
358 }
359
360 int ProjectSettings::proxyMinSize() const
361 {
362     return proxy_minsize->value();
363 }
364
365 int ProjectSettings::proxyImageMinSize() const
366 {
367     return proxy_imageminsize->value();
368 }
369
370 QString ProjectSettings::proxyParams() const
371 {
372     return proxy_params->toPlainText();
373 }
374
375 QString ProjectSettings::proxyExtension() const
376 {
377     return proxy_extension->text();
378 }
379
380 //static
381 QStringList ProjectSettings::extractPlaylistUrls(QString path)
382 {
383     QStringList urls;
384     QDomDocument doc;
385     QFile file(path);
386     if (!file.open(QIODevice::ReadOnly))
387         return urls;
388     if (!doc.setContent(&file)) {
389         file.close();
390         return urls;
391     }
392     file.close();
393     QString root = doc.documentElement().attribute("root");
394     if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
395     QDomNodeList files = doc.elementsByTagName("producer");
396     for (int i = 0; i < files.count(); i++) {
397         QDomElement e = files.at(i).toElement();
398         QString type = EffectsList::property(e, "mlt_service");
399         if (type != "colour") {
400             QString url = EffectsList::property(e, "resource");
401             if (!url.isEmpty()) {
402                 if (!url.startsWith('/')) url.prepend(root);
403                 if (url.section('.', 0, -2).endsWith("/.all")) {
404                     // slideshow clip, extract image urls
405                     urls << extractSlideshowUrls(KUrl(url));
406                 } else urls << url;
407                 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
408                     //TODO: Do something to avoid infinite loops if 2 files reference themselves...
409                     urls << extractPlaylistUrls(url);
410                 }
411             }
412         }
413     }
414
415     // luma files for transitions
416     files = doc.elementsByTagName("transition");
417     for (int i = 0; i < files.count(); i++) {
418         QDomElement e = files.at(i).toElement();
419         QString url = EffectsList::property(e, "luma");
420         if (!url.isEmpty()) {
421             if (!url.startsWith('/')) url.prepend(root);
422             urls << url;
423         }
424     }
425
426     return urls;
427 }
428
429
430 //static
431 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
432 {
433     QStringList urls;
434     QString path = url.directory(KUrl::AppendTrailingSlash);
435     QString ext = url.path().section('.', -1);
436     QDir dir(path);
437     if (url.path().contains(".all.")) {
438         // this is a mime slideshow, like *.jpeg
439         QStringList filters;
440         filters << "*." + ext;
441         dir.setNameFilters(filters);
442         QStringList result = dir.entryList(QDir::Files);
443         urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
444     } else {
445         // this is a pattern slideshow, like sequence%4d.jpg
446         QString filter = url.fileName();
447         QString ext = filter.section('.', -1);
448         filter = filter.section('%', 0, -2);
449         QString regexp = "^" + filter + "\\d+\\." + ext + "$";
450         QRegExp rx(regexp);
451         int count = 0;
452         QStringList result = dir.entryList(QDir::Files);
453         foreach(const QString & path, result) {
454             if (rx.exactMatch(path)) count++;
455         }
456         urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
457     }
458     return urls;
459 }
460
461 void ProjectSettings::slotExportToText()
462 {
463     QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
464     if (savePath.isEmpty()) return;
465     QString data;
466     data.append(i18n("Project folder: %1",  project_folder->url().path()) + "\n");
467     data.append(i18n("Project profile: %1",  profiles_list->currentText()) + "\n");
468     data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
469     for (int i = 0; i < files_list->topLevelItemCount(); i++) {
470         if (files_list->topLevelItem(i)->childCount() > 0) {
471             data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
472             for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
473                 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
474             }
475         }
476     }
477     KTemporaryFile tmpfile;
478     if (!tmpfile.open()) {
479         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
480         return;
481     }
482     QFile xmlf(tmpfile.fileName());
483     xmlf.open(QIODevice::WriteOnly);
484     xmlf.write(data.toUtf8());
485     if (xmlf.error() != QFile::NoError) {
486         xmlf.close();
487         return;
488     }
489     xmlf.close();
490     KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
491 }
492
493
494
495 #include "projectsettings.moc"
496
497