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 KConfig conf("encodingprofiles.rc", KConfig::FullConfig, "appdata");
98 KConfigGroup group(&conf, "proxy");
99 QMap <QString, QString> values = group.entryMap();
100 QMapIterator<QString, QString> k(values);
102 while (k.hasNext()) {
104 if (!k.key().isEmpty()) {
105 QString params = k.value().section(';', 0, 0);
106 QString extension = k.value().section(';', 1, 1);
107 if (ix == -1 && ((params == proxyparameters && extension == proxyextension) || (proxyparameters.isEmpty() || proxyextension.isEmpty()))) {
108 // this is the current profile
109 ix = proxy_profile->count();
111 proxy_profile->addItem(k.key(), k.value());
115 // Current project proxy settings not found
116 ix = proxy_profile->count();
117 proxy_profile->addItem(i18n("Current Settings"), QString(proxyparameters + ';' + proxyextension));
119 proxy_profile->setCurrentIndex(ix);
120 slotUpdateProxyParams();
123 proxy_showprofileinfo->setIcon(KIcon("help-about"));
124 connect(proxy_profile, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateProxyParams()));
125 proxyparams->setVisible(false);
126 proxyparams->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
127 connect(proxy_showprofileinfo, SIGNAL(clicked(bool)), proxyparams, SLOT(setVisible(bool)));
129 if (readOnlyTracks) {
130 video_tracks->setEnabled(false);
131 audio_tracks->setEnabled(false);
134 if (m_projectList != NULL) {
136 connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
137 connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
138 connect(delete_proxies, SIGNAL(clicked()), this, SLOT(slotDeleteProxies()));
139 } else tabWidget->widget(1)->setEnabled(false);
140 connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
141 connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
142 connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
145 void ProjectSettings::slotDeleteUnused()
147 QStringList toDelete;
148 QList <DocClipBase*> list = m_projectList->documentClipList();
149 for (int i = 0; i < list.count(); i++) {
150 DocClipBase *clip = list.at(i);
151 if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
152 KUrl url = clip->fileURL();
153 if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
157 // make sure our urls are not used in another clip
158 for (int i = 0; i < list.count(); i++) {
159 DocClipBase *clip = list.at(i);
160 if (clip->numReferences() > 0) {
161 KUrl url = clip->fileURL();
162 if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
166 if (toDelete.count() == 0) {
167 // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
168 if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
169 m_projectList->cleanup();
173 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;
174 m_projectList->trashUnusedClips();
178 void ProjectSettings::slotClearCache()
180 buttonBox->setEnabled(false);
181 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
182 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
183 buttonBox->setEnabled(true);
184 slotUpdateFiles(true);
187 void ProjectSettings::slotDeleteProxies()
189 if (KMessageBox::warningContinueCancel(this, i18n("Deleting proxy clips will disable proxies for this project.")) != KMessageBox::Continue) return;
190 buttonBox->setEnabled(false);
191 enable_proxy->setChecked(false);
192 emit disableProxies();
193 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
194 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
195 buttonBox->setEnabled(true);
196 slotUpdateFiles(true);
199 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
201 KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
203 thumbs_count->setText(QString::number(job->totalFiles()));
204 thumbs_size->setText(KIO::convertSize(job->totalSize()));
205 job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
207 proxy_count->setText(QString::number(job->totalFiles()));
208 proxy_size->setText(KIO::convertSize(job->totalSize()));
210 if (cacheOnly) return;
213 KIO::filesize_t usedSize = 0;
214 KIO::filesize_t unUsedSize = 0;
215 QList <DocClipBase*> list = m_projectList->documentClipList();
218 // List all files that are used in the project. That also means:
219 // images included in slideshow and titles, files in playlist clips
220 // TODO: images used in luma transitions?
223 QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
224 videos->setIcon(0, KIcon("video-x-generic"));
225 videos->setExpanded(true);
226 QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
227 sounds->setIcon(0, KIcon("audio-x-generic"));
228 sounds->setExpanded(true);
229 QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
230 images->setIcon(0, KIcon("image-x-generic"));
231 images->setExpanded(true);
232 QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
233 slideshows->setIcon(0, KIcon("image-x-generic"));
234 slideshows->setExpanded(true);
235 QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
236 texts->setIcon(0, KIcon("text-plain"));
237 texts->setExpanded(true);
238 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
239 others->setIcon(0, KIcon("unknown"));
240 others->setExpanded(true);
242 QStringList allFonts;
243 foreach(const QString & file, m_lumas) {
245 new QTreeWidgetItem(images, QStringList() << file);
248 for (int i = 0; i < list.count(); i++) {
249 DocClipBase *clip = list.at(i);
250 if (clip->clipType() == SLIDESHOW) {
251 QStringList subfiles = extractSlideshowUrls(clip->fileURL());
252 foreach(const QString & file, subfiles) {
254 new QTreeWidgetItem(slideshows, QStringList() << file);
256 } else if (!clip->fileURL().isEmpty()) {
257 //allFiles.append(clip->fileURL().path());
258 switch (clip->clipType()) {
260 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
263 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
266 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
269 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
272 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
277 if (clip->clipType() == TEXT) {
278 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
279 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
280 foreach(const QString & file, imagefiles) {
282 new QTreeWidgetItem(images, QStringList() << file);
285 } else if (clip->clipType() == PLAYLIST) {
286 QStringList files = extractPlaylistUrls(clip->fileURL().path());
287 foreach(const QString & file, files) {
289 new QTreeWidgetItem(others, QStringList() << file);
293 if (clip->numReferences() == 0) {
295 unUsedSize += clip->fileSize();
298 usedSize += clip->fileSize();
301 #if QT_VERSION >= 0x040500
302 allFonts.removeDuplicates();
304 // Hide unused categories
305 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
306 if (files_list->topLevelItem(i)->childCount() == 0) {
307 files_list->topLevelItem(i)->setHidden(true);
310 files_count->setText(QString::number(count));
311 fonts_list->addItems(allFonts);
312 if (allFonts.isEmpty()) {
313 fonts_list->setHidden(true);
314 label_fonts->setHidden(true);
316 used_count->setText(QString::number(used));
317 used_size->setText(KIO::convertSize(usedSize));
318 unused_count->setText(QString::number(unused));
319 unused_size->setText(KIO::convertSize(unUsedSize));
320 delete_unused->setEnabled(unused > 0);
323 void ProjectSettings::accept()
325 if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
326 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;
330 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 locale.toString((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"