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 proxy_params->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
69 connect(generate_proxy, SIGNAL(toggled(bool)), proxy_minsize, SLOT(setEnabled(bool)));
70 connect(generate_imageproxy, SIGNAL(toggled(bool)), proxy_imageminsize, SLOT(setEnabled(bool)));
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 proxy_params->setPlainText(projectlist->getDocumentProperty("proxyparams"));
77 generate_imageproxy->setChecked(projectlist->getDocumentProperty("generateimageproxy").toInt());
78 proxy_imageminsize->setValue(projectlist->getDocumentProperty("proxyimageminsize").toInt());
79 proxy_extension->setText(projectlist->getDocumentProperty("proxyextension"));
82 enable_proxy->setChecked(KdenliveSettings::enableproxy());
83 generate_proxy->setChecked(KdenliveSettings::generateproxy());
84 proxy_minsize->setValue(KdenliveSettings::proxyminsize());
85 proxy_params->setPlainText(KdenliveSettings::proxyparams());
86 generate_imageproxy->setChecked(KdenliveSettings::generateimageproxy());
87 proxy_imageminsize->setValue(KdenliveSettings::proxyimageminsize());
88 proxy_extension->setText(KdenliveSettings::proxyextension());
93 video_tracks->setEnabled(false);
94 audio_tracks->setEnabled(false);
97 if (m_projectList != NULL) {
99 connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
100 connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
101 connect(delete_proxies, SIGNAL(clicked()), this, SLOT(slotDeleteProxies()));
102 } else tabWidget->widget(1)->setEnabled(false);
103 connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
104 connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
105 connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
108 void ProjectSettings::slotDeleteUnused()
110 QStringList toDelete;
111 QList <DocClipBase*> list = m_projectList->documentClipList();
112 for (int i = 0; i < list.count(); i++) {
113 DocClipBase *clip = list.at(i);
114 if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
115 KUrl url = clip->fileURL();
116 if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
120 // make sure our urls are not used in another clip
121 for (int i = 0; i < list.count(); i++) {
122 DocClipBase *clip = list.at(i);
123 if (clip->numReferences() > 0) {
124 KUrl url = clip->fileURL();
125 if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
129 if (toDelete.count() == 0) {
130 // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
131 if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
132 m_projectList->cleanup();
136 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;
137 m_projectList->trashUnusedClips();
141 void ProjectSettings::slotClearCache()
143 buttonBox->setEnabled(false);
144 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
145 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
146 buttonBox->setEnabled(true);
147 slotUpdateFiles(true);
150 void ProjectSettings::slotDeleteProxies()
152 buttonBox->setEnabled(false);
154 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
155 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
156 buttonBox->setEnabled(true);
157 slotUpdateFiles(true);
160 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
162 KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
164 thumbs_count->setText(QString::number(job->totalFiles()));
165 thumbs_size->setText(KIO::convertSize(job->totalSize()));
166 job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
168 proxy_count->setText(QString::number(job->totalFiles()));
169 proxy_size->setText(KIO::convertSize(job->totalSize()));
171 if (cacheOnly) return;
174 KIO::filesize_t usedSize = 0;
175 KIO::filesize_t unUsedSize = 0;
176 QList <DocClipBase*> list = m_projectList->documentClipList();
179 // List all files that are used in the project. That also means:
180 // images included in slideshow and titles, files in playlist clips
181 // TODO: images used in luma transitions, files used for LADSPA effects?
184 QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
185 videos->setIcon(0, KIcon("video-x-generic"));
186 videos->setExpanded(true);
187 QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
188 sounds->setIcon(0, KIcon("audio-x-generic"));
189 sounds->setExpanded(true);
190 QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
191 images->setIcon(0, KIcon("image-x-generic"));
192 images->setExpanded(true);
193 QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
194 slideshows->setIcon(0, KIcon("image-x-generic"));
195 slideshows->setExpanded(true);
196 QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
197 texts->setIcon(0, KIcon("text-plain"));
198 texts->setExpanded(true);
199 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
200 others->setIcon(0, KIcon("unknown"));
201 others->setExpanded(true);
203 QStringList allFonts;
204 foreach(const QString & file, m_lumas) {
206 new QTreeWidgetItem(images, QStringList() << file);
209 for (int i = 0; i < list.count(); i++) {
210 DocClipBase *clip = list.at(i);
211 if (clip->clipType() == SLIDESHOW) {
212 QStringList subfiles = extractSlideshowUrls(clip->fileURL());
213 foreach(const QString & file, subfiles) {
215 new QTreeWidgetItem(slideshows, QStringList() << file);
217 } else if (!clip->fileURL().isEmpty()) {
218 //allFiles.append(clip->fileURL().path());
219 switch (clip->clipType()) {
221 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
224 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
227 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
230 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
233 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
238 if (clip->clipType() == TEXT) {
239 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
240 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
241 foreach(const QString & file, imagefiles) {
243 new QTreeWidgetItem(images, QStringList() << file);
246 } else if (clip->clipType() == PLAYLIST) {
247 QStringList files = extractPlaylistUrls(clip->fileURL().path());
248 foreach(const QString & file, files) {
250 new QTreeWidgetItem(others, QStringList() << file);
254 if (clip->numReferences() == 0) {
256 unUsedSize += clip->fileSize();
259 usedSize += clip->fileSize();
262 #if QT_VERSION >= 0x040500
263 allFonts.removeDuplicates();
265 // Hide unused categories
266 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
267 if (files_list->topLevelItem(i)->childCount() == 0) {
268 files_list->topLevelItem(i)->setHidden(true);
271 files_count->setText(QString::number(count));
272 fonts_list->addItems(allFonts);
273 if (allFonts.isEmpty()) {
274 fonts_list->setHidden(true);
275 label_fonts->setHidden(true);
277 used_count->setText(QString::number(used));
278 used_size->setText(KIO::convertSize(usedSize));
279 unused_count->setText(QString::number(unused));
280 unused_size->setText(KIO::convertSize(unUsedSize));
281 delete_unused->setEnabled(unused > 0);
284 void ProjectSettings::accept()
286 if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
287 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;
291 void ProjectSettings::slotUpdateDisplay()
293 QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
294 QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
295 p_size->setText(values.value("width") + 'x' + values.value("height"));
296 p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
297 p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
298 p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
299 if (values.value("progressive").toInt() == 0) {
300 p_progressive->setText(i18n("Interlaced (%1 fields per second)",
301 QString::number((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
303 p_progressive->setText(i18n("Progressive"));
305 p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
308 void ProjectSettings::slotUpdateButton(const QString &path)
310 if (path.isEmpty()) m_buttonOk->setEnabled(false);
312 m_buttonOk->setEnabled(true);
313 slotUpdateFiles(true);
317 QString ProjectSettings::selectedProfile() const
319 return profiles_list->itemData(profiles_list->currentIndex()).toString();
322 KUrl ProjectSettings::selectedFolder() const
324 return project_folder->url();
327 QPoint ProjectSettings::tracks()
330 p.setX(video_tracks->value());
331 p.setY(audio_tracks->value());
335 bool ProjectSettings::enableVideoThumbs() const
337 return video_thumbs->isChecked();
340 bool ProjectSettings::enableAudioThumbs() const
342 return audio_thumbs->isChecked();
345 bool ProjectSettings::useProxy() const
347 return enable_proxy->isChecked();
350 bool ProjectSettings::generateProxy() const
352 return generate_proxy->isChecked();
355 bool ProjectSettings::generateImageProxy() const
357 return generate_imageproxy->isChecked();
360 int ProjectSettings::proxyMinSize() const
362 return proxy_minsize->value();
365 int ProjectSettings::proxyImageMinSize() const
367 return proxy_imageminsize->value();
370 QString ProjectSettings::proxyParams() const
372 return proxy_params->toPlainText();
375 QString ProjectSettings::proxyExtension() const
377 return proxy_extension->text();
381 QStringList ProjectSettings::extractPlaylistUrls(QString path)
386 if (!file.open(QIODevice::ReadOnly))
388 if (!doc.setContent(&file)) {
393 QString root = doc.documentElement().attribute("root");
394 if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
395 QDomNodeList files = doc.elementsByTagName("producer");
396 for (int i = 0; i < files.count(); i++) {
397 QDomElement e = files.at(i).toElement();
398 QString type = EffectsList::property(e, "mlt_service");
399 if (type != "colour") {
400 QString url = EffectsList::property(e, "resource");
401 if (!url.isEmpty()) {
402 if (!url.startsWith('/')) url.prepend(root);
403 if (url.section('.', 0, -2).endsWith("/.all")) {
404 // slideshow clip, extract image urls
405 urls << extractSlideshowUrls(KUrl(url));
407 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
408 //TODO: Do something to avoid infinite loops if 2 files reference themselves...
409 urls << extractPlaylistUrls(url);
415 // luma files for transitions
416 files = doc.elementsByTagName("transition");
417 for (int i = 0; i < files.count(); i++) {
418 QDomElement e = files.at(i).toElement();
419 QString url = EffectsList::property(e, "luma");
420 if (!url.isEmpty()) {
421 if (!url.startsWith('/')) url.prepend(root);
431 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
434 QString path = url.directory(KUrl::AppendTrailingSlash);
435 QString ext = url.path().section('.', -1);
437 if (url.path().contains(".all.")) {
438 // this is a mime slideshow, like *.jpeg
440 filters << "*." + ext;
441 dir.setNameFilters(filters);
442 QStringList result = dir.entryList(QDir::Files);
443 urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
445 // this is a pattern slideshow, like sequence%4d.jpg
446 QString filter = url.fileName();
447 QString ext = filter.section('.', -1);
448 filter = filter.section('%', 0, -2);
449 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
452 QStringList result = dir.entryList(QDir::Files);
453 foreach(const QString & path, result) {
454 if (rx.exactMatch(path)) count++;
456 urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
461 void ProjectSettings::slotExportToText()
463 QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
464 if (savePath.isEmpty()) return;
466 data.append(i18n("Project folder: %1", project_folder->url().path()) + "\n");
467 data.append(i18n("Project profile: %1", profiles_list->currentText()) + "\n");
468 data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
469 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
470 if (files_list->topLevelItem(i)->childCount() > 0) {
471 data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
472 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
473 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
477 KTemporaryFile tmpfile;
478 if (!tmpfile.open()) {
479 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
482 QFile xmlf(tmpfile.fileName());
483 xmlf.open(QIODevice::WriteOnly);
484 xmlf.write(data.toUtf8());
485 if (xmlf.error() != QFile::NoError) {
490 KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
495 #include "projectsettings.moc"