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 allFonts.removeDuplicates();
302 // Hide unused categories
303 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
304 if (files_list->topLevelItem(i)->childCount() == 0) {
305 files_list->topLevelItem(i)->setHidden(true);
308 files_count->setText(QString::number(count));
309 fonts_list->addItems(allFonts);
310 if (allFonts.isEmpty()) {
311 fonts_list->setHidden(true);
312 label_fonts->setHidden(true);
314 used_count->setText(QString::number(used));
315 used_size->setText(KIO::convertSize(usedSize));
316 unused_count->setText(QString::number(unused));
317 unused_size->setText(KIO::convertSize(unUsedSize));
318 delete_unused->setEnabled(unused > 0);
321 void ProjectSettings::accept()
323 if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
324 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;
328 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 locale.toString((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"