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()
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 QString::number((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
343 p_progressive->setText(i18n("Progressive"));
345 p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
348 void ProjectSettings::slotUpdateButton(const QString &path)
350 if (path.isEmpty()) m_buttonOk->setEnabled(false);
352 m_buttonOk->setEnabled(true);
353 slotUpdateFiles(true);
357 QString ProjectSettings::selectedProfile() const
359 return profiles_list->itemData(profiles_list->currentIndex()).toString();
362 KUrl ProjectSettings::selectedFolder() const
364 return project_folder->url();
367 QPoint ProjectSettings::tracks()
370 p.setX(video_tracks->value());
371 p.setY(audio_tracks->value());
375 bool ProjectSettings::enableVideoThumbs() const
377 return video_thumbs->isChecked();
380 bool ProjectSettings::enableAudioThumbs() const
382 return audio_thumbs->isChecked();
385 bool ProjectSettings::useProxy() const
387 return enable_proxy->isChecked();
390 bool ProjectSettings::generateProxy() const
392 return generate_proxy->isChecked();
395 bool ProjectSettings::generateImageProxy() const
397 return generate_imageproxy->isChecked();
400 int ProjectSettings::proxyMinSize() const
402 return proxy_minsize->value();
405 int ProjectSettings::proxyImageMinSize() const
407 return proxy_imageminsize->value();
410 QString ProjectSettings::proxyParams() const
412 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
413 return params.section(';', 0, 0);
416 QString ProjectSettings::proxyExtension() const
418 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
419 return params.section(';', 1, 1);
423 QStringList ProjectSettings::extractPlaylistUrls(QString path)
428 if (!file.open(QIODevice::ReadOnly))
430 if (!doc.setContent(&file)) {
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));
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);
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);
473 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
476 QString path = url.directory(KUrl::AppendTrailingSlash);
477 QString ext = url.path().section('.', -1);
479 if (url.path().contains(".all.")) {
480 // this is a mime slideshow, like *.jpeg
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()) + ")");
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 + "$";
494 QStringList result = dir.entryList(QDir::Files);
495 foreach(const QString & path, result) {
496 if (rx.exactMatch(path)) count++;
498 urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
503 void ProjectSettings::slotExportToText()
505 QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
506 if (savePath.isEmpty()) return;
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");
519 KTemporaryFile tmpfile;
520 if (!tmpfile.open()) {
521 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
524 QFile xmlf(tmpfile.fileName());
525 xmlf.open(QIODevice::WriteOnly);
526 xmlf.write(data.toUtf8());
527 if (xmlf.error() != QFile::NoError) {
532 KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
535 void ProjectSettings::slotUpdateProxyParams()
537 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
538 proxyparams->setPlainText(params.section(';', 0, 0));
541 #include "projectsettings.moc"