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 buttonBox->setEnabled(false);
192 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
193 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
194 buttonBox->setEnabled(true);
195 slotUpdateFiles(true);
198 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
200 KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
202 thumbs_count->setText(QString::number(job->totalFiles()));
203 thumbs_size->setText(KIO::convertSize(job->totalSize()));
204 job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
206 proxy_count->setText(QString::number(job->totalFiles()));
207 proxy_size->setText(KIO::convertSize(job->totalSize()));
209 if (cacheOnly) return;
212 KIO::filesize_t usedSize = 0;
213 KIO::filesize_t unUsedSize = 0;
214 QList <DocClipBase*> list = m_projectList->documentClipList();
217 // List all files that are used in the project. That also means:
218 // images included in slideshow and titles, files in playlist clips
219 // TODO: images used in luma transitions, files used for LADSPA effects?
222 QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
223 videos->setIcon(0, KIcon("video-x-generic"));
224 videos->setExpanded(true);
225 QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
226 sounds->setIcon(0, KIcon("audio-x-generic"));
227 sounds->setExpanded(true);
228 QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
229 images->setIcon(0, KIcon("image-x-generic"));
230 images->setExpanded(true);
231 QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
232 slideshows->setIcon(0, KIcon("image-x-generic"));
233 slideshows->setExpanded(true);
234 QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
235 texts->setIcon(0, KIcon("text-plain"));
236 texts->setExpanded(true);
237 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
238 others->setIcon(0, KIcon("unknown"));
239 others->setExpanded(true);
241 QStringList allFonts;
242 foreach(const QString & file, m_lumas) {
244 new QTreeWidgetItem(images, QStringList() << file);
247 for (int i = 0; i < list.count(); i++) {
248 DocClipBase *clip = list.at(i);
249 if (clip->clipType() == SLIDESHOW) {
250 QStringList subfiles = extractSlideshowUrls(clip->fileURL());
251 foreach(const QString & file, subfiles) {
253 new QTreeWidgetItem(slideshows, QStringList() << file);
255 } else if (!clip->fileURL().isEmpty()) {
256 //allFiles.append(clip->fileURL().path());
257 switch (clip->clipType()) {
259 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
262 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
265 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
268 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
271 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
276 if (clip->clipType() == TEXT) {
277 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
278 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
279 foreach(const QString & file, imagefiles) {
281 new QTreeWidgetItem(images, QStringList() << file);
284 } else if (clip->clipType() == PLAYLIST) {
285 QStringList files = extractPlaylistUrls(clip->fileURL().path());
286 foreach(const QString & file, files) {
288 new QTreeWidgetItem(others, QStringList() << file);
292 if (clip->numReferences() == 0) {
294 unUsedSize += clip->fileSize();
297 usedSize += clip->fileSize();
300 #if QT_VERSION >= 0x040500
301 allFonts.removeDuplicates();
303 // Hide unused categories
304 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
305 if (files_list->topLevelItem(i)->childCount() == 0) {
306 files_list->topLevelItem(i)->setHidden(true);
309 files_count->setText(QString::number(count));
310 fonts_list->addItems(allFonts);
311 if (allFonts.isEmpty()) {
312 fonts_list->setHidden(true);
313 label_fonts->setHidden(true);
315 used_count->setText(QString::number(used));
316 used_size->setText(KIO::convertSize(usedSize));
317 unused_count->setText(QString::number(unused));
318 unused_size->setText(KIO::convertSize(unUsedSize));
319 delete_unused->setEnabled(unused > 0);
322 void ProjectSettings::accept()
324 if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
325 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;
329 void ProjectSettings::slotUpdateDisplay()
331 QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
332 QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
333 p_size->setText(values.value("width") + 'x' + values.value("height"));
334 p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
335 p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
336 p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
337 if (values.value("progressive").toInt() == 0) {
338 p_progressive->setText(i18n("Interlaced (%1 fields per second)",
339 QString::number((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
341 p_progressive->setText(i18n("Progressive"));
343 p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
346 void ProjectSettings::slotUpdateButton(const QString &path)
348 if (path.isEmpty()) m_buttonOk->setEnabled(false);
350 m_buttonOk->setEnabled(true);
351 slotUpdateFiles(true);
355 QString ProjectSettings::selectedProfile() const
357 return profiles_list->itemData(profiles_list->currentIndex()).toString();
360 KUrl ProjectSettings::selectedFolder() const
362 return project_folder->url();
365 QPoint ProjectSettings::tracks()
368 p.setX(video_tracks->value());
369 p.setY(audio_tracks->value());
373 bool ProjectSettings::enableVideoThumbs() const
375 return video_thumbs->isChecked();
378 bool ProjectSettings::enableAudioThumbs() const
380 return audio_thumbs->isChecked();
383 bool ProjectSettings::useProxy() const
385 return enable_proxy->isChecked();
388 bool ProjectSettings::generateProxy() const
390 return generate_proxy->isChecked();
393 bool ProjectSettings::generateImageProxy() const
395 return generate_imageproxy->isChecked();
398 int ProjectSettings::proxyMinSize() const
400 return proxy_minsize->value();
403 int ProjectSettings::proxyImageMinSize() const
405 return proxy_imageminsize->value();
408 QString ProjectSettings::proxyParams() const
410 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
411 return params.section(';', 0, 0);
414 QString ProjectSettings::proxyExtension() const
416 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
417 return params.section(';', 1, 1);
421 QStringList ProjectSettings::extractPlaylistUrls(QString path)
426 if (!file.open(QIODevice::ReadOnly))
428 if (!doc.setContent(&file)) {
433 QString root = doc.documentElement().attribute("root");
434 if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
435 QDomNodeList files = doc.elementsByTagName("producer");
436 for (int i = 0; i < files.count(); i++) {
437 QDomElement e = files.at(i).toElement();
438 QString type = EffectsList::property(e, "mlt_service");
439 if (type != "colour") {
440 QString url = EffectsList::property(e, "resource");
441 if (!url.isEmpty()) {
442 if (!url.startsWith('/')) url.prepend(root);
443 if (url.section('.', 0, -2).endsWith("/.all")) {
444 // slideshow clip, extract image urls
445 urls << extractSlideshowUrls(KUrl(url));
447 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
448 //TODO: Do something to avoid infinite loops if 2 files reference themselves...
449 urls << extractPlaylistUrls(url);
455 // luma files for transitions
456 files = doc.elementsByTagName("transition");
457 for (int i = 0; i < files.count(); i++) {
458 QDomElement e = files.at(i).toElement();
459 QString url = EffectsList::property(e, "luma");
460 if (!url.isEmpty()) {
461 if (!url.startsWith('/')) url.prepend(root);
471 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
474 QString path = url.directory(KUrl::AppendTrailingSlash);
475 QString ext = url.path().section('.', -1);
477 if (url.path().contains(".all.")) {
478 // this is a mime slideshow, like *.jpeg
480 filters << "*." + ext;
481 dir.setNameFilters(filters);
482 QStringList result = dir.entryList(QDir::Files);
483 urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
485 // this is a pattern slideshow, like sequence%4d.jpg
486 QString filter = url.fileName();
487 QString ext = filter.section('.', -1);
488 filter = filter.section('%', 0, -2);
489 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
492 QStringList result = dir.entryList(QDir::Files);
493 foreach(const QString & path, result) {
494 if (rx.exactMatch(path)) count++;
496 urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
501 void ProjectSettings::slotExportToText()
503 QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
504 if (savePath.isEmpty()) return;
506 data.append(i18n("Project folder: %1", project_folder->url().path()) + "\n");
507 data.append(i18n("Project profile: %1", profiles_list->currentText()) + "\n");
508 data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
509 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
510 if (files_list->topLevelItem(i)->childCount() > 0) {
511 data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
512 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
513 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
517 KTemporaryFile tmpfile;
518 if (!tmpfile.open()) {
519 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
522 QFile xmlf(tmpfile.fileName());
523 xmlf.open(QIODevice::WriteOnly);
524 xmlf.write(data.toUtf8());
525 if (xmlf.error() != QFile::NoError) {
530 KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
533 void ProjectSettings::slotUpdateProxyParams()
535 QString params = proxy_profile->itemData(proxy_profile->currentIndex()).toString();
536 proxyparams->setPlainText(params.section(';', 0, 0));
539 #include "projectsettings.moc"