]> git.sesse.net Git - kdenlive/blob - src/projectsettings.cpp
Check clips property before launching proxy creation so that we only proxy the HD...
[kdenlive] / src / projectsettings.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *                                                                         *
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.                                   *
8  *                                                                         *
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.                          *
13  *                                                                         *
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  ***************************************************************************/
19
20 #include "projectsettings.h"
21 #include "kdenlivesettings.h"
22 #include "profilesdialog.h"
23 #include "docclipbase.h"
24 #include "titlewidget.h"
25 #include "effectslist.h"
26
27 #include <KStandardDirs>
28 #include <KMessageBox>
29 #include <KDebug>
30 #include <kio/directorysizejob.h>
31 #include <KIO/NetAccess>
32 #include <KTemporaryFile>
33 #include <KFileDialog>
34
35 #include <QDir>
36 #include <kmessagebox.h>
37
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)
40 {
41     setupUi(this);
42
43     list_search->setTreeWidget(files_list);
44
45     QMap <QString, QString> profilesInfo = ProfilesDialog::getProfilesInfo();
46     QMapIterator<QString, QString> i(profilesInfo);
47     while (i.hasNext()) {
48         i.next();
49         profiles_list->addItem(i.key(), i.value());
50     }
51     project_folder->setMode(KFile::Directory);
52     project_folder->setUrl(KUrl(projectPath));
53     QString currentProf = KdenliveSettings::current_profile();
54
55     for (int i = 0; i < profiles_list->count(); i++) {
56         if (profiles_list->itemData(i).toString() == currentProf) {
57             profiles_list->setCurrentIndex(i);
58             break;
59         }
60     }
61
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)));
70     
71     if (projectlist) {
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());
77     }
78     else {
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());
84     }
85     
86     if (readOnlyTracks) {
87         video_tracks->setEnabled(false);
88         audio_tracks->setEnabled(false);
89     }
90     slotUpdateDisplay();
91     if (m_projectList != NULL) {
92         slotUpdateFiles();
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()));
99 }
100
101 void ProjectSettings::slotDeleteUnused()
102 {
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();
110         }
111     }
112
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());
119         }
120     }
121
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();
126         slotUpdateFiles();
127         return;
128     }
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();
131     slotUpdateFiles();
132 }
133
134 void ProjectSettings::slotClearCache()
135 {
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);
141 }
142
143 void ProjectSettings::slotUpdateFiles(bool cacheOnly)
144 {
145     KIO::DirectorySizeJob * job = KIO::directorySize(project_folder->url().path(KUrl::AddTrailingSlash) + "thumbs/");
146     job->exec();
147     thumbs_count->setText(QString::number(job->totalFiles()));
148     thumbs_size->setText(KIO::convertSize(job->totalSize()));
149     delete job;
150     if (cacheOnly) return;
151     int unused = 0;
152     int used = 0;
153     KIO::filesize_t usedSize = 0;
154     KIO::filesize_t unUsedSize = 0;
155     QList <DocClipBase*> list = m_projectList->documentClipList();
156     files_list->clear();
157
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?
161
162     // Setup categories
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);
181     int count = 0;
182     QStringList allFonts;
183     foreach(const QString & file, m_lumas) {
184         count++;
185         new QTreeWidgetItem(images, QStringList() << file);
186     }
187
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) {
193                 count++;
194                 new QTreeWidgetItem(slideshows, QStringList() << file);
195             }
196         } else if (!clip->fileURL().isEmpty()) {
197             //allFiles.append(clip->fileURL().path());
198             switch (clip->clipType()) {
199             case TEXT:
200                 new QTreeWidgetItem(texts, QStringList() << clip->fileURL().path());
201                 break;
202             case AUDIO:
203                 new QTreeWidgetItem(sounds, QStringList() << clip->fileURL().path());
204                 break;
205             case IMAGE:
206                 new QTreeWidgetItem(images, QStringList() << clip->fileURL().path());
207                 break;
208             case UNKNOWN:
209                 new QTreeWidgetItem(others, QStringList() << clip->fileURL().path());
210                 break;
211             default:
212                 new QTreeWidgetItem(videos, QStringList() << clip->fileURL().path());
213                 break;
214             }
215             count++;
216         }
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) {
221                 count++;
222                 new QTreeWidgetItem(images, QStringList() << file);
223             }
224             allFonts << fonts;
225         } else if (clip->clipType() == PLAYLIST) {
226             QStringList files = extractPlaylistUrls(clip->fileURL().path());
227             foreach(const QString & file, files) {
228                 count++;
229                 new QTreeWidgetItem(others, QStringList() << file);
230             }
231         }
232
233         if (clip->numReferences() == 0) {
234             unused++;
235             unUsedSize += clip->fileSize();
236         } else {
237             used++;
238             usedSize += clip->fileSize();
239         }
240     }
241 #if QT_VERSION >= 0x040500
242     allFonts.removeDuplicates();
243 #endif
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);
248         }
249     }
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);
255     }
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);
261 }
262
263 void ProjectSettings::accept()
264 {
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;
267     QDialog::accept();
268 }
269
270 void ProjectSettings::slotUpdateDisplay()
271 {
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)));
281     } else {
282         p_progressive->setText(i18n("Progressive"));
283     }
284     p_colorspace->setText(ProfilesDialog::getColorspaceDescription(values.value("colorspace").toInt()));
285 }
286
287 void ProjectSettings::slotUpdateButton(const QString &path)
288 {
289     if (path.isEmpty()) m_buttonOk->setEnabled(false);
290     else {
291         m_buttonOk->setEnabled(true);
292         slotUpdateFiles(true);
293     }
294 }
295
296 QString ProjectSettings::selectedProfile() const
297 {
298     return profiles_list->itemData(profiles_list->currentIndex()).toString();
299 }
300
301 KUrl ProjectSettings::selectedFolder() const
302 {
303     return project_folder->url();
304 }
305
306 QPoint ProjectSettings::tracks()
307 {
308     QPoint p;
309     p.setX(video_tracks->value());
310     p.setY(audio_tracks->value());
311     return p;
312 }
313
314 bool ProjectSettings::enableVideoThumbs() const
315 {
316     return video_thumbs->isChecked();
317 }
318
319 bool ProjectSettings::enableAudioThumbs() const
320 {
321     return audio_thumbs->isChecked();
322 }
323
324 bool ProjectSettings::useProxy() const
325 {
326     return enable_proxy->isChecked();
327 }
328
329 bool ProjectSettings::generateProxy() const
330 {
331     return generate_proxy->isChecked();
332 }
333
334 int ProjectSettings::proxyMinSize() const
335 {
336     return proxy_minsize->value();
337 }
338
339 QString ProjectSettings::proxyParams() const
340 {
341     return proxy_params->toPlainText();
342 }
343
344 //static
345 QStringList ProjectSettings::extractPlaylistUrls(QString path)
346 {
347     QStringList urls;
348     QDomDocument doc;
349     QFile file(path);
350     if (!file.open(QIODevice::ReadOnly))
351         return urls;
352     if (!doc.setContent(&file)) {
353         file.close();
354         return urls;
355     }
356     file.close();
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));
370                 } else urls << 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);
374                 }
375             }
376         }
377     }
378
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);
386             urls << url;
387         }
388     }
389
390     return urls;
391 }
392
393
394 //static
395 QStringList ProjectSettings::extractSlideshowUrls(KUrl url)
396 {
397     QStringList urls;
398     QString path = url.directory(KUrl::AppendTrailingSlash);
399     QString ext = url.path().section('.', -1);
400     QDir dir(path);
401     if (url.path().contains(".all.")) {
402         // this is a mime slideshow, like *.jpeg
403         QStringList filters;
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()) + ")");
408     } else {
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 + "$";
414         QRegExp rx(regexp);
415         int count = 0;
416         QStringList result = dir.entryList(QDir::Files);
417         foreach(const QString & path, result) {
418             if (rx.exactMatch(path)) count++;
419         }
420         urls.append(url.path() + " (" + i18np("1 image found", "%1 images found", count) + ")");
421     }
422     return urls;
423 }
424
425 void ProjectSettings::slotExportToText()
426 {
427     QString savePath = KFileDialog::getSaveFileName(project_folder->url(), "text/plain", this);
428     if (savePath.isEmpty()) return;
429     QString data;
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");
438             }
439         }
440     }
441     KTemporaryFile tmpfile;
442     if (!tmpfile.open()) {
443         kWarning() << "/////  CANNOT CREATE TMP FILE in: " << tmpfile.fileName();
444         return;
445     }
446     QFile xmlf(tmpfile.fileName());
447     xmlf.open(QIODevice::WriteOnly);
448     xmlf.write(data.toUtf8());
449     if (xmlf.error() != QFile::NoError) {
450         xmlf.close();
451         return;
452     }
453     xmlf.close();
454     KIO::NetAccess::upload(tmpfile.fileName(), savePath, 0);
455 }
456
457
458
459 #include "projectsettings.moc"
460
461