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(enable_proxy, SIGNAL(toggled(bool)), proxy_box, SLOT(setEnabled(bool)));
69 connect(generate_proxy, SIGNAL(toggled(bool)), proxy_minsize, SLOT(setEnabled(bool)));
72 enable_proxy->setChecked(projectlist->useProxy());
73 generate_proxy->setChecked(projectlist->generateProxy());
74 proxy_minsize->setValue(projectlist->proxyMinSize());
75 proxy_params->setText(projectlist->proxyParams());
76 proxy_box->setEnabled(projectlist->useProxy());
79 enable_proxy->setChecked(KdenliveSettings::enableproxy());
80 generate_proxy->setChecked(KdenliveSettings::enableproxy());
81 proxy_minsize->setValue(1000);
82 proxy_params->setText(KdenliveSettings::proxyparams());
83 proxy_box->setEnabled(KdenliveSettings::enableproxy());
87 video_tracks->setEnabled(false);
88 audio_tracks->setEnabled(false);
91 if (m_projectList != NULL) {
93 connect(clear_cache, SIGNAL(clicked()), this, SLOT(slotClearCache()));
94 connect(delete_unused, SIGNAL(clicked()), this, SLOT(slotDeleteUnused()));
95 } else tabWidget->widget(1)->setEnabled(false);
96 connect(profiles_list, SIGNAL(currentIndexChanged(int)), this, SLOT(slotUpdateDisplay()));
97 connect(project_folder, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButton(const QString &)));
98 connect(button_export, SIGNAL(clicked()), this, SLOT(slotExportToText()));
101 void ProjectSettings::slotDeleteUnused()
103 QStringList toDelete;
104 QList <DocClipBase*> list = m_projectList->documentClipList();
105 for (int i = 0; i < list.count(); i++) {
106 DocClipBase *clip = list.at(i);
107 if (clip->numReferences() == 0 && clip->clipType() != SLIDESHOW) {
108 KUrl url = clip->fileURL();
109 if (!url.isEmpty() && !toDelete.contains(url.path())) toDelete << url.path();
113 // make sure our urls are not used in another clip
114 for (int i = 0; i < list.count(); i++) {
115 DocClipBase *clip = list.at(i);
116 if (clip->numReferences() > 0) {
117 KUrl url = clip->fileURL();
118 if (!url.isEmpty() && toDelete.contains(url.path())) toDelete.removeAll(url.path());
122 if (toDelete.count() == 0) {
123 // No physical url to delete, we only remove unused clips from project (color clips for example have no physical url)
124 if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return;
125 m_projectList->cleanup();
129 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;
130 m_projectList->trashUnusedClips();
134 void ProjectSettings::slotClearCache()
136 buttonBox->setEnabled(false);
137 KIO::NetAccess::del(KUrl(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/"), this);
138 KStandardDirs::makeDir(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
139 buttonBox->setEnabled(true);
140 slotUpdateFiles(true);
143 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
145 KIO::DirectorySizeJob * job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
147 thumbs_count->setText(QString::number(job->totalFiles()));
148 thumbs_size->setText(KIO::convertSize(job->totalSize()));
150 if (cacheOnly) return;
153 KIO::filesize_t usedSize = 0;
154 KIO::filesize_t unUsedSize = 0;
155 QList <DocClipBase*> list = m_projectList->documentClipList();
158 // List all files that are used in the project. That also means:
159 // images included in slideshow and titles, files in playlist clips
160 // TODO: images used in luma transitions, files used for LADSPA effects?
163 QTreeWidgetItem *videos = new QTreeWidgetItem(files_list, QStringList() << i18n("Video clips"));
164 videos->setIcon(0, KIcon("video-x-generic"));
165 videos->setExpanded(true);
166 QTreeWidgetItem *sounds = new QTreeWidgetItem(files_list, QStringList() << i18n("Audio clips"));
167 sounds->setIcon(0, KIcon("audio-x-generic"));
168 sounds->setExpanded(true);
169 QTreeWidgetItem *images = new QTreeWidgetItem(files_list, QStringList() << i18n("Image clips"));
170 images->setIcon(0, KIcon("image-x-generic"));
171 images->setExpanded(true);
172 QTreeWidgetItem *slideshows = new QTreeWidgetItem(files_list, QStringList() << i18n("Slideshow clips"));
173 slideshows->setIcon(0, KIcon("image-x-generic"));
174 slideshows->setExpanded(true);
175 QTreeWidgetItem *texts = new QTreeWidgetItem(files_list, QStringList() << i18n("Text clips"));
176 texts->setIcon(0, KIcon("text-plain"));
177 texts->setExpanded(true);
178 QTreeWidgetItem *others = new QTreeWidgetItem(files_list, QStringList() << i18n("Other clips"));
179 others->setIcon(0, KIcon("unknown"));
180 others->setExpanded(true);
182 QStringList allFonts;
183 foreach(const QString & file, m_lumas) {
185 new QTreeWidgetItem(images, QStringList() << file);
188 for (int i = 0; i < list.count(); i++) {
189 DocClipBase *clip = list.at(i);
190 if (clip->clipType() == SLIDESHOW) {
191 QStringList subfiles = extractSlideshowUrls(clip->fileURL());
192 foreach(const QString & file, subfiles) {
194 new QTreeWidgetItem(slideshows, QStringList() << file);
196 } else if (!clip->fileURL().isEmpty()) {
197 //allFiles.append(clip->fileURL().path());
198 switch (clip->clipType()) {
200 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
203 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
206 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
209 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
212 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
217 if (clip->clipType() == TEXT) {
218 QStringList imagefiles = TitleWidget::extractImageList(clip->getProperty("xmldata"));
219 QStringList fonts = TitleWidget::extractFontList(clip->getProperty("xmldata"));
220 foreach(const QString & file, imagefiles) {
222 new QTreeWidgetItem(images, QStringList() << file);
225 } else if (clip->clipType() == PLAYLIST) {
226 QStringList files = extractPlaylistUrls(clip->fileURL().path());
227 foreach(const QString & file, files) {
229 new QTreeWidgetItem(others, QStringList() << file);
233 if (clip->numReferences() == 0) {
235 unUsedSize += clip->fileSize();
238 usedSize += clip->fileSize();
241 #if QT_VERSION >= 0x040500
242 allFonts.removeDuplicates();
244 // Hide unused categories
245 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
246 if (files_list->topLevelItem(i)->childCount() == 0) {
247 files_list->topLevelItem(i)->setHidden(true);
250 files_count->setText(QString::number(count));
251 fonts_list->addItems(allFonts);
252 if (allFonts.isEmpty()) {
253 fonts_list->setHidden(true);
254 label_fonts->setHidden(true);
256 used_count->setText(QString::number(used));
257 used_size->setText(KIO::convertSize(usedSize));
258 unused_count->setText(QString::number(unused));
259 unused_size->setText(KIO::convertSize(unUsedSize));
260 delete_unused->setEnabled(unused > 0);
263 void ProjectSettings::accept()
265 if (!m_savedProject && selectedProfile() != KdenliveSettings::current_profile())
266 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;
270 void ProjectSettings::slotUpdateDisplay()
272 QString currentProfile = profiles_list->itemData(profiles_list->currentIndex()).toString();
273 QMap< QString, QString > values = ProfilesDialog::getSettingsFromFile(currentProfile);
274 p_size->setText(values.value("width") + 'x' + values.value("height"));
275 p_fps->setText(values.value("frame_rate_num") + '/' + values.value("frame_rate_den"));
276 p_aspect->setText(values.value("sample_aspect_num") + '/' + values.value("sample_aspect_den"));
277 p_display->setText(values.value("display_aspect_num") + '/' + values.value("display_aspect_den"));
278 if (values.value("progressive").toInt() == 0) {
279 p_progressive->setText(i18n("Interlaced (%1 fields per second)",
280 QString::number((double)2 * values.value("frame_rate_num").toInt() / values.value("frame_rate_den").toInt(), 'f', 2)));
282 p_progressive->setText(i18n("Progressive"));
284 p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
287 void ProjectSettings::slotUpdateButton(const QString &path)
289 if (path.isEmpty()) m_buttonOk->setEnabled(false);
291 m_buttonOk->setEnabled(true);
292 slotUpdateFiles(true);
296 QString ProjectSettings::selectedProfile() const
298 return profiles_list->itemData(profiles_list->currentIndex()).toString();
301 KUrl ProjectSettings::selectedFolder() const
303 return project_folder->url();
306 QPoint ProjectSettings::tracks()
309 p.setX(video_tracks->value());
310 p.setY(audio_tracks->value());
314 bool ProjectSettings::enableVideoThumbs() const
316 return video_thumbs->isChecked();
319 bool ProjectSettings::enableAudioThumbs() const
321 return audio_thumbs->isChecked();
324 bool ProjectSettings::useProxy() const
326 return enable_proxy->isChecked();
329 bool ProjectSettings::generateProxy() const
331 return generate_proxy->isChecked();
334 int ProjectSettings::proxyMinSize() const
336 return proxy_minsize->value();
339 QString ProjectSettings::proxyParams() const
341 return proxy_params->toPlainText();
345 QStringList ProjectSettings::extractPlaylistUrls(QString path)
350 if (!file.open(QIODevice::ReadOnly))
352 if (!doc.setContent(&file)) {
357 QString root = doc.documentElement().attribute("root");
358 if (!root.isEmpty() && !root.endsWith('/')) root.append('/');
359 QDomNodeList files = doc.elementsByTagName("producer");
360 for (int i = 0; i < files.count(); i++) {
361 QDomElement e = files.at(i).toElement();
362 QString type = EffectsList::property(e, "mlt_service");
363 if (type != "colour") {
364 QString url = EffectsList::property(e, "resource");
365 if (!url.isEmpty()) {
366 if (!url.startsWith('/')) url.prepend(root);
367 if (url.section('.', 0, -2).endsWith("/.all")) {
368 // slideshow clip, extract image urls
369 urls << extractSlideshowUrls(KUrl(url));
371 if (url.endsWith(".mlt") || url.endsWith(".kdenlive")) {
372 //TODO: Do something to avoid infinite loops if 2 files reference themselves...
373 urls << extractPlaylistUrls(url);
379 // luma files for transitions
380 files = doc.elementsByTagName("transition");
381 for (int i = 0; i < files.count(); i++) {
382 QDomElement e = files.at(i).toElement();
383 QString url = EffectsList::property(e, "luma");
384 if (!url.isEmpty()) {
385 if (!url.startsWith('/')) url.prepend(root);
395 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
398 QString path = url.directory(KUrl::AppendTrailingSlash);
399 QString ext = url.path().section('.', -1);
401 if (url.path().contains(".all.")) {
402 // this is a mime slideshow, like *.jpeg
404 filters << "*." + ext;
405 dir.setNameFilters(filters);
406 QStringList result = dir.entryList(QDir::Files);
407 urls.append(path + filters.at(0) + " (" + i18np("1 image found", "%1 images found", result.count()) + ")");
409 // this is a pattern slideshow, like sequence%4d.jpg
410 QString filter = url.fileName();
411 QString ext = filter.section('.', -1);
412 filter = filter.section('%', 0, -2);
413 QString regexp = "^" + filter + "\\d+\\." + ext + "$";
416 QStringList result = dir.entryList(QDir::Files);
417 foreach(const QString & path, result) {
418 if (rx.exactMatch(path)) count++;
420 urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
425 void ProjectSettings::slotExportToText()
427 QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
428 if (savePath.isEmpty()) return;
430 data.append(i18n("Project folder: %1", project_folder->url().path()) + "\n");
431 data.append(i18n("Project profile: %1", profiles_list->currentText()) + "\n");
432 data.append(i18n("Total clips: %1 (%2 used in timeline).", files_count->text(), used_count->text()) + "\n\n");
433 for (int i = 0; i < files_list->topLevelItemCount(); i++) {
434 if (files_list->topLevelItem(i)->childCount() > 0) {
435 data.append("\n" + files_list->topLevelItem(i)->text(0) + ":\n\n");
436 for (int j = 0; j < files_list->topLevelItem(i)->childCount(); j++) {
437 data.append(files_list->topLevelItem(i)->child(j)->text(0) + "\n");
441 KTemporaryFile tmpfile;
442 if (!tmpfile.open()) {
443 kWarning() << "///// CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
446 QFile xmlf(tmpfile.fileName());
447 xmlf.open(QIODevice::WriteOnly);
448 xmlf.write(data.toUtf8());
449 if (xmlf.error() != QFile::NoError) {
454 KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
459 #include "projectsettings.moc"