1 /***************************************************************************
2 * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
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. *
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. *
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 ***************************************************************************/
20 #include "projectsettings.h"
21 #include "kdenlivesettings.h"
22 #include "profilesdialog.h"
23 #include "docclipbase.h"
24 #include "titlewidget.h"
25 #include "effectslist.h"
27 #include <KStandardDirs>
28 #include <KMessageBox>
30 #include <kio/directorysizejob.h>
31 #include <KIO/NetAccess>
32 #include <KTemporaryFile>
33 #include <KFileDialog>
36 #include <kmessagebox.h>
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)
43 list_search->setTreeWidget(files_list);
45 QMap <QString, QString> profilesInfo = ProfilesDialog::getProfilesInfo();
46 QMapIterator<QString, QString> i(profilesInfo);
49 profiles_list->addItem(i.key(), i.value());
51 project_folder->setMode(KFile::Directory);
52 project_folder->setUrl(KUrl(projectPath));
53 QString currentProf = KdenliveSettings::current_profile();
55 for (int i = 0; i < profiles_list->count(); i++) {
56 if (profiles_list->itemData(i).toString() == currentProf) {
57 profiles_list->setCurrentIndex(i);
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;
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");
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();
92 proxy_minsize->setEnabled(generate_proxy->isChecked());
93 proxy_imageminsize->setEnabled(generate_imageproxy->isChecked());
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);
103 while (k.hasNext()) {
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();
112 proxy_profile->addItem(k.key(), k.value());
116 // Current project proxy settings not found
117 ix = proxy_profile->count();
118 proxy_profile->addItem(i18n("Current Settings"), QString(proxyparameters + ';' + proxyextension));
120 proxy_profile->setCurrentIndex(ix);
121 slotUpdateProxyParams();
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)));
130 if (readOnlyTracks) {
131 video_tracks->setEnabled(false);
132 audio_tracks->setEnabled(false);
135 if (m_projectList != NULL) {
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()));
146 void ProjectSettings::slotDeleteUnused()
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();
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());
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();
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();
179 void ProjectSettings::slotClearCache()
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);
188 void ProjectSettings::slotDeleteProxies()
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);
200 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
202 KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
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/");
208 proxy_count->setText(QString::number(job->totalFiles()));
209 proxy_size->setText(KIO::convertSize(job->totalSize()));
211 if (cacheOnly) return;
214 KIO::filesize_t usedSize = 0;
215 KIO::filesize_t unUsedSize = 0;
216 QList <DocClipBase*> list = m_projectList->documentClipList();
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?
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);
243 QStringList allFonts;
244 foreach(const QString & file, m_lumas) {
246 new QTreeWidgetItem(images, QStringList() << file);
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) {
255 new QTreeWidgetItem(slideshows, QStringList() << file);
257 } else if (!clip->fileURL().isEmpty()) {
258 //allFiles.append(clip->fileURL().path());
259 switch (clip->clipType()) {
261 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
264 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
267 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
270 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
273 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
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) {
283 new QTreeWidgetItem(images, QStringList() << file);
286 } else if (clip->clipType() == PLAYLIST) {
287 QStringList files = extractPlaylistUrls(clip->fileURL().path());
288 foreach(const QString & file, files) {
290 new QTreeWidgetItem(others, QStringList() << file);
294 if (clip->numReferences() == 0) {
296 unUsedSize += clip->fileSize();
299 usedSize += clip->fileSize();
302 #if QT_VERSION >= 0x040500
303 allFonts.removeDuplicates();
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);
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);
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);
324 void ProjectSettings::accept()
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;
331 void ProjectSettings::slotUpdateDisplay()
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)));
344 p_progressive->setText(i18n("Progressive"));
346 p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
349 void ProjectSettings::slotUpdateButton(const QString &path)
351 if (path.isEmpty()) m_buttonOk->setEnabled(false);
353 m_buttonOk->setEnabled(true);
354 slotUpdateFiles(true);
358 QString ProjectSettings::selectedProfile() const
360 return profiles_list->itemData(profiles_list->currentIndex()).toString();
363 KUrl ProjectSettings::selectedFolder() const
365 return project_folder->url();
368 QPoint ProjectSettings::tracks()
371 p.setX(video_tracks->value());
372 p.setY(audio_tracks->value());
376 bool ProjectSettings::enableVideoThumbs() const
378 return video_thumbs->isChecked();
381 bool ProjectSettings::enableAudioThumbs() const
383 return audio_thumbs->isChecked();
386 bool ProjectSettings::useProxy() const
388 return enable_proxy->isChecked();
391 bool ProjectSettings::generateProxy() const
393 return generate_proxy->isChecked();
396 bool ProjectSettings::generateImageProxy() const
398 return generate_imageproxy->isChecked();
401 int ProjectSettings::proxyMinSize() const
403 return proxy_minsize->value();
406 int ProjectSettings::proxyImageMinSize() const
408 return proxy_imageminsize->value();
411 QString ProjectSettings::proxyParams() const
413 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
414 return params.section(';', 0, 0);
417 QString ProjectSettings::proxyExtension() const
419 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
420 return params.section(';', 1, 1);
424 QStringList ProjectSettings::extractPlaylistUrls(QString path)
429 if (!file.open(QIODevice::ReadOnly))
431 if (!doc.setContent(&file)) {
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));
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);
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);
474 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
477 QString path = url.directory(KUrl::AppendTrailingSlash);
478 QString ext = url.path().section('.', -1);
480 if (url.path().contains(".all.")) {
481 // this is a mime slideshow, like *.jpeg
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()) + ")");
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 + "$";
495 QStringList result = dir.entryList(QDir::Files);
496 foreach(const QString & path, result) {
497 if (rx.exactMatch(path)) count++;
499 urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
504 void ProjectSettings::slotExportToText()
506 QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
507 if (savePath.isEmpty()) return;
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");
520 KTemporaryFile tmpfile;
521 if (!tmpfile.open()) {
522 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
525 QFile xmlf(tmpfile.fileName());
526 xmlf.open(QIODevice::WriteOnly);
527 xmlf.write(data.toUtf8());
528 if (xmlf.error() != QFile::NoError) {
533 KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
536 void ProjectSettings::slotUpdateProxyParams()
538 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
539 proxyparams->setPlainText(params.section(';', 0, 0));
542 #include "projectsettings.moc"