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