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());
92 proxy_minsize->setEnabled(generate_proxy->isChecked());
93 proxy_imageminsize->setEnabled(generate_imageproxy->isChecked());
96 video_tracks->setEnabled(false);
97 audio_tracks->setEnabled(false);
100 if (m_projectList != NULL) {
102 connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
103 connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
104 connect(delete_proxies, SIGNAL(clicked()), this, SLOT(slotDeleteProxies()));
105 } else tabWidget->widget(1)->setEnabled(false);
106 connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
107 connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
108 connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
111 void ProjectSettings::slotDeleteUnused()
113 QStringList toDelete;
114 QList <DocClipBase*> list = m_projectList->documentClipList();
115 for (int i = 0; i < list.count(); i++) {
116 DocClipBase *clip = list.at(i);
117 if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
118 KUrl url = clip->fileURL();
119 if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
123 // make sure our urls are not used in another clip
124 for (int i = 0; i < list.count(); i++) {
125 DocClipBase *clip = list.at(i);
126 if (clip->numReferences() > 0) {
127 KUrl url = clip->fileURL();
128 if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
132 if (toDelete.count() == 0) {
133 // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
134 if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
135 m_projectList->cleanup();
139 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;
140 m_projectList->trashUnusedClips();
144 void ProjectSettings::slotClearCache()
146 buttonBox->setEnabled(false);
147 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
148 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
149 buttonBox->setEnabled(true);
150 slotUpdateFiles(true);
153 void ProjectSettings::slotDeleteProxies()
155 buttonBox->setEnabled(false);
157 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/"), this);
158 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
159 buttonBox->setEnabled(true);
160 slotUpdateFiles(true);
163 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
165 KIO::DirectorySizeJob *job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
167 thumbs_count->setText(QString::number(job->totalFiles()));
168 thumbs_size->setText(KIO::convertSize(job->totalSize()));
169 job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "proxy/");
171 proxy_count->setText(QString::number(job->totalFiles()));
172 proxy_size->setText(KIO::convertSize(job->totalSize()));
174 if (cacheOnly) return;
177 KIO::filesize_t usedSize = 0;
178 KIO::filesize_t unUsedSize = 0;
179 QList <DocClipBase*> list = m_projectList->documentClipList();
182 // List all files that are used in the project. That also means:
183 // images included in slideshow and titles, files in playlist clips
184 // TODO: images used in luma transitions, files used for LADSPA effects?
187 QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
188 videos->setIcon(0, KIcon("video-x-generic"));
189 videos->setExpanded(true);
190 QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
191 sounds->setIcon(0, KIcon("audio-x-generic"));
192 sounds->setExpanded(true);
193 QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
194 images->setIcon(0, KIcon("image-x-generic"));
195 images->setExpanded(true);
196 QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
197 slideshows->setIcon(0, KIcon("image-x-generic"));
198 slideshows->setExpanded(true);
199 QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
200 texts->setIcon(0, KIcon("text-plain"));
201 texts->setExpanded(true);
202 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
203 others->setIcon(0, KIcon("unknown"));
204 others->setExpanded(true);
206 QStringList allFonts;
207 foreach(const QString & file, m_lumas) {
209 new QTreeWidgetItem(images, QStringList() << file);
212 for (int i = 0; i < list.count(); i++) {
213 DocClipBase *clip = list.at(i);
214 if (clip->clipType() == SLIDESHOW) {
215 QStringList subfiles = extractSlideshowUrls(clip->fileURL());
216 foreach(const QString & file, subfiles) {
218 new QTreeWidgetItem(slideshows, QStringList() << file);
220 } else if (!clip->fileURL().isEmpty()) {
221 //allFiles.append(clip->fileURL().path());
222 switch (clip->clipType()) {
224 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
227 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
230 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
233 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
236 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
241 if (clip->clipType() == TEXT) {
242 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
243 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
244 foreach(const QString & file, imagefiles) {
246 new QTreeWidgetItem(images, QStringList() << file);
249 } else if (clip->clipType() == PLAYLIST) {
250 QStringList files = extractPlaylistUrls(clip->fileURL().path());
251 foreach(const QString & file, files) {
253 new QTreeWidgetItem(others, QStringList() << file);
257 if (clip->numReferences() == 0) {
259 unUsedSize += clip->fileSize();
262 usedSize += clip->fileSize();
265 #if QT_VERSION >= 0x040500
266 allFonts.removeDuplicates();
268 // Hide unused categories
269 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
270 if (files_list->topLevelItem(i)->childCount() == 0) {
271 files_list->topLevelItem(i)->setHidden(true);
274 files_count->setText(QString::number(count));
275 fonts_list->addItems(allFonts);
276 if (allFonts.isEmpty()) {
277 fonts_list->setHidden(true);
278 label_fonts->setHidden(true);
280 used_count->setText(QString::number(used));
281 used_size->setText(KIO::convertSize(usedSize));
282 unused_count->setText(QString::number(unused));
283 unused_size->setText(KIO::convertSize(unUsedSize));
284 delete_unused->setEnabled(unused > 0);
287 void ProjectSettings::accept()
289 if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
290 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;
294 void ProjectSettings::slotUpdateDisplay()
296 QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
297 QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
298 p_size->setText(values.value("width") + 'x' + values.value("height"));
299 p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
300 p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
301 p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
302 if (values.value("progressive").toInt() == 0) {
303 p_progressive->setText(i18n("Interlaced (%1 fields per second)",
304 QString::number((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
306 p_progressive->setText(i18n("Progressive"));
308 p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
311 void ProjectSettings::slotUpdateButton(const QString &path)
313 if (path.isEmpty()) m_buttonOk->setEnabled(false);
315 m_buttonOk->setEnabled(true);
316 slotUpdateFiles(true);
320 QString ProjectSettings::selectedProfile() const
322 return profiles_list->itemData(profiles_list->currentIndex()).toString();
325 KUrl ProjectSettings::selectedFolder() const
327 return project_folder->url();
330 QPoint ProjectSettings::tracks()
333 p.setX(video_tracks->value());
334 p.setY(audio_tracks->value());
338 bool ProjectSettings::enableVideoThumbs() const
340 return video_thumbs->isChecked();
343 bool ProjectSettings::enableAudioThumbs() const
345 return audio_thumbs->isChecked();
348 bool ProjectSettings::useProxy() const
350 return enable_proxy->isChecked();
353 bool ProjectSettings::generateProxy() const
355 return generate_proxy->isChecked();
358 bool ProjectSettings::generateImageProxy() const
360 return generate_imageproxy->isChecked();
363 int ProjectSettings::proxyMinSize() const
365 return proxy_minsize->value();
368 int ProjectSettings::proxyImageMinSize() const
370 return proxy_imageminsize->value();
373 QString ProjectSettings::proxyParams() const
375 return proxy_params->toPlainText();
378 QString ProjectSettings::proxyExtension() const
380 return proxy_extension->text();
384 QStringList ProjectSettings::extractPlaylistUrls(QString path)
389 if (!file.open(QIODevice::ReadOnly))
391 if (!doc.setContent(&file)) {
396 QString root = doc.documentElement().attribute("root");
397 if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
398 QDomNodeList files = doc.elementsByTagName("producer");
399 for (int i = 0; i < files.count(); i++) {
400 QDomElement e = files.at(i).toElement();
401 QString type = EffectsList::property(e, "mlt_service");
402 if (type != "colour") {
403 QString url = EffectsList::property(e, "resource");
404 if (!url.isEmpty()) {
405 if (!url.startsWith('/')) url.prepend(root);
406 if (url.section('.', 0, -2).endsWith("/.all")) {
407 // slideshow clip, extract image urls
408 urls << extractSlideshowUrls(KUrl(url));
410 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
411 //TODO: Do something to avoid infinite loops if 2 files reference themselves...
412 urls << extractPlaylistUrls(url);
418 // luma files for transitions
419 files = doc.elementsByTagName("transition");
420 for (int i = 0; i < files.count(); i++) {
421 QDomElement e = files.at(i).toElement();
422 QString url = EffectsList::property(e, "luma");
423 if (!url.isEmpty()) {
424 if (!url.startsWith('/')) url.prepend(root);
434 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
437 QString path = url.directory(KUrl::AppendTrailingSlash);
438 QString ext = url.path().section('.', -1);
440 if (url.path().contains(".all.")) {
441 // this is a mime slideshow, like *.jpeg
443 filters << "*." + ext;
444 dir.setNameFilters(filters);
445 QStringList result = dir.entryList(QDir::Files);
446 urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
448 // this is a pattern slideshow, like sequence%4d.jpg
449 QString filter = url.fileName();
450 QString ext = filter.section('.', -1);
451 filter = filter.section('%', 0, -2);
452 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
455 QStringList result = dir.entryList(QDir::Files);
456 foreach(const QString & path, result) {
457 if (rx.exactMatch(path)) count++;
459 urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
464 void ProjectSettings::slotExportToText()
466 QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
467 if (savePath.isEmpty()) return;
469 data.append(i18n("Project folder: %1", project_folder->url().path()) + "\n");
470 data.append(i18n("Project profile: %1", profiles_list->currentText()) + "\n");
471 data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
472 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
473 if (files_list->topLevelItem(i)->childCount() > 0) {
474 data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
475 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
476 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
480 KTemporaryFile tmpfile;
481 if (!tmpfile.open()) {
482 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
485 QFile xmlf(tmpfile.fileName());
486 xmlf.open(QIODevice::WriteOnly);
487 xmlf.write(data.toUtf8());
488 if (xmlf.error() != QFile::NoError) {
493 KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
498 #include "projectsettings.moc"