]> git.sesse.net Git - kdenlive/blob - src/renderwidget.cpp
Hide export profiles that resize video ( size is different than project profile)...
[kdenlive] / src / renderwidget.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
21 #include <QDomDocument>
22
23 #include <KStandardDirs>
24 #include <KDebug>
25 #include <KMessageBox>
26 #include <KComboBox>
27
28 #include "kdenlivesettings.h"
29 #include "renderwidget.h"
30 #include "ui_saveprofile_ui.h"
31
32 const int GroupRole = Qt::UserRole;
33 const int ExtensionRole = GroupRole + 1;
34 const int StandardRole = GroupRole + 2;
35 const int RenderRole = GroupRole + 3;
36 const int ParamsRole = GroupRole + 4;
37 const int EditableRole = GroupRole + 5;
38
39 RenderWidget::RenderWidget(QWidget * parent): QDialog(parent) {
40     m_view.setupUi(this);
41     m_view.buttonDelete->setIcon(KIcon("trash-empty"));
42     m_view.buttonDelete->setToolTip(i18n("Delete profile"));
43     m_view.buttonDelete->setEnabled(false);
44
45     m_view.buttonEdit->setIcon(KIcon("document-properties"));
46     m_view.buttonEdit->setToolTip(i18n("Edit profile"));
47     m_view.buttonEdit->setEnabled(false);
48
49     m_view.buttonSave->setIcon(KIcon("document-new"));
50     m_view.buttonSave->setToolTip(i18n("Create new profile"));
51
52     m_view.buttonInfo->setIcon(KIcon("help-about"));
53
54     if (KdenliveSettings::showrenderparams()) {
55         m_view.buttonInfo->setDown(true);
56     } else m_view.advanced_params->hide();
57
58     m_view.experimentalrender->setChecked(KdenliveSettings::experimentalrender());
59
60     connect(m_view.buttonInfo, SIGNAL(clicked()), this, SLOT(showInfoPanel()));
61
62     connect(m_view.buttonSave, SIGNAL(clicked()), this, SLOT(slotSaveProfile()));
63     connect(m_view.buttonEdit, SIGNAL(clicked()), this, SLOT(slotEditProfile()));
64     connect(m_view.buttonDelete, SIGNAL(clicked()), this, SLOT(slotDeleteProfile()));
65     connect(m_view.buttonStart, SIGNAL(clicked()), this, SLOT(slotExport()));
66     connect(m_view.out_file, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButtons()));
67     connect(m_view.format_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshView()));
68     connect(m_view.size_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshParams()));
69
70     connect(m_view.render_guide, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
71     connect(m_view.render_zone, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
72     connect(m_view.render_full, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
73
74     connect(m_view.guide_end, SIGNAL(activated(int)), this, SLOT(slotCheckStartGuidePosition()));
75     connect(m_view.guide_start, SIGNAL(activated(int)), this, SLOT(slotCheckEndGuidePosition()));
76
77     connect(m_view.format_selection, SIGNAL(activated(int)), this, SLOT(refreshView()));
78     connect(m_view.experimentalrender, SIGNAL(stateChanged(int)), this, SLOT(slotUpdateExperimentalRendering()));
79
80     m_view.buttonStart->setEnabled(false);
81     m_view.guides_box->setVisible(false);
82     parseProfiles();
83     m_view.splitter->setStretchFactor(1, 5);
84     m_view.splitter->setStretchFactor(0, 2);
85
86     focusFirstVisibleItem();
87 }
88
89 void RenderWidget::slotUpdateExperimentalRendering() {
90     KdenliveSettings::setExperimentalrender(m_view.experimentalrender->isChecked());
91     refreshView();
92 }
93
94
95 void RenderWidget::showInfoPanel() {
96     if (m_view.advanced_params->isVisible()) {
97         m_view.advanced_params->setVisible(false);
98         m_view.buttonInfo->setDown(false);
99         KdenliveSettings::setShowrenderparams(false);
100     } else {
101         m_view.advanced_params->setVisible(true);
102         m_view.buttonInfo->setDown(true);
103         KdenliveSettings::setShowrenderparams(true);
104     }
105 }
106
107 void RenderWidget::slotUpdateGuideBox() {
108     m_view.guides_box->setVisible(m_view.render_guide->isChecked());
109 }
110
111 void RenderWidget::slotCheckStartGuidePosition() {
112     if (m_view.guide_start->currentIndex() > m_view.guide_end->currentIndex())
113         m_view.guide_start->setCurrentIndex(m_view.guide_end->currentIndex());
114 }
115
116 void RenderWidget::slotCheckEndGuidePosition() {
117     if (m_view.guide_end->currentIndex() < m_view.guide_start->currentIndex())
118         m_view.guide_end->setCurrentIndex(m_view.guide_start->currentIndex());
119 }
120
121 void RenderWidget::setGuides(QDomElement guidesxml, double duration) {
122     m_view.guide_start->clear();
123     m_view.guide_end->clear();
124     QDomNodeList nodes = guidesxml.elementsByTagName("guide");
125     if (nodes.count() > 0) {
126         m_view.guide_start->addItem(i18n("Render"), "0");
127         m_view.render_guide->setEnabled(true);
128     } else m_view.render_guide->setEnabled(false);
129     for (int i = 0; i < nodes.count(); i++) {
130         QDomElement e = nodes.item(i).toElement();
131         if (!e.isNull()) {
132             m_view.guide_start->addItem(e.attribute("comment"), e.attribute("time").toDouble());
133             m_view.guide_end->addItem(e.attribute("comment"), e.attribute("time").toDouble());
134         }
135     }
136     if (nodes.count() > 0)
137         m_view.guide_end->addItem(i18n("End"), QString::number(duration));
138 }
139
140 void RenderWidget::slotUpdateButtons() {
141     if (m_view.out_file->url().isEmpty()) m_view.buttonStart->setEnabled(false);
142     else m_view.buttonStart->setEnabled(true);
143 }
144
145 void RenderWidget::slotSaveProfile() {
146     Ui::SaveProfile_UI ui;
147     QDialog *d = new QDialog(this);
148     ui.setupUi(d);
149     QString customGroup = i18n("Custom");
150     QStringList groupNames;
151     for (int i = 0; i < m_view.format_list->count(); i++)
152         groupNames.append(m_view.format_list->item(i)->text());
153     if (!groupNames.contains(customGroup)) groupNames.prepend(customGroup);
154     ui.group_name->addItems(groupNames);
155     int pos = ui.group_name->findText(customGroup);
156     ui.group_name->setCurrentIndex(pos);
157
158     ui.parameters->setText(m_view.advanced_params->toPlainText());
159     ui.extension->setText(m_view.size_list->currentItem()->data(ExtensionRole).toString());
160     ui.profile_name->setFocus();
161     if (d->exec() == QDialog::Accepted && !ui.profile_name->text().simplified().isEmpty()) {
162         QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
163         QDomDocument doc;
164         QFile file(exportFile);
165         doc.setContent(&file, false);
166         file.close();
167
168         QDomElement documentElement;
169         bool groupExists = false;
170         QString groupName;
171         QString newProfileName = ui.profile_name->text().simplified();
172         QString newGroupName = ui.group_name->currentText();
173         QDomNodeList groups = doc.elementsByTagName("group");
174         int i = 0;
175         if (groups.count() == 0) {
176             QDomElement profiles = doc.createElement("profiles");
177             doc.appendChild(profiles);
178         } else while (!groups.item(i).isNull()) {
179                 documentElement = groups.item(i).toElement();
180                 groupName = documentElement.attribute("name");
181                 kDebug() << "// SAVE, PARSING FROUP: " << i << ", name: " << groupName << ", LOOK FR: " << newGroupName;
182                 if (groupName == newGroupName) {
183                     groupExists = true;
184                     break;
185                 }
186                 i++;
187             }
188         if (!groupExists) {
189             documentElement = doc.createElement("group");
190             documentElement.setAttribute("name", ui.group_name->currentText());
191             documentElement.setAttribute("renderer", "avformat");
192             doc.documentElement().appendChild(documentElement);
193         }
194         QDomElement profileElement = doc.createElement("profile");
195         profileElement.setAttribute("name", newProfileName);
196         profileElement.setAttribute("extension", ui.extension->text().simplified());
197         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
198         documentElement.appendChild(profileElement);
199
200         //QCString save = doc.toString().utf8();
201
202         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
203             KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
204             delete d;
205             return;
206         }
207         QTextStream out(&file);
208         out << doc.toString();
209         file.close();
210         parseProfiles(newGroupName, newProfileName);
211     }
212     delete d;
213 }
214
215 void RenderWidget::slotEditProfile() {
216     QListWidgetItem *item = m_view.size_list->currentItem();
217     if (!item) return;
218     QString currentGroup = m_view.format_list->currentItem()->text();
219
220     QString params = item->data(ParamsRole).toString();
221     QString extension = item->data(ExtensionRole).toString();
222     QString currentProfile = item->text();
223
224     Ui::SaveProfile_UI ui;
225     QDialog *d = new QDialog(this);
226     ui.setupUi(d);
227     QStringList groupNames;
228     for (int i = 0; i < m_view.format_list->count(); i++)
229         groupNames.append(m_view.format_list->item(i)->text());
230     if (!groupNames.contains(currentGroup)) groupNames.prepend(currentGroup);
231     ui.group_name->addItems(groupNames);
232     int pos = ui.group_name->findText(currentGroup);
233     ui.group_name->setCurrentIndex(pos);
234     ui.profile_name->setText(currentProfile);
235     ui.extension->setText(extension);
236     ui.parameters->setText(params);
237     ui.profile_name->setFocus();
238
239     if (d->exec() == QDialog::Accepted) {
240         slotDeleteProfile();
241         QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
242         QDomDocument doc;
243         QFile file(exportFile);
244         doc.setContent(&file, false);
245         file.close();
246
247         QDomElement documentElement;
248         bool groupExists = false;
249         QString groupName;
250         QString newProfileName = ui.profile_name->text();
251         QString newGroupName = ui.group_name->currentText();
252         QDomNodeList groups = doc.elementsByTagName("group");
253         int i = 0;
254         if (groups.count() == 0) {
255             QDomElement profiles = doc.createElement("profiles");
256             doc.appendChild(profiles);
257         } else while (!groups.item(i).isNull()) {
258                 documentElement = groups.item(i).toElement();
259                 groupName = documentElement.attribute("name");
260                 kDebug() << "// SAVE, PARSING FROUP: " << i << ", name: " << groupName << ", LOOK FR: " << newGroupName;
261                 if (groupName == newGroupName) {
262                     groupExists = true;
263                     break;
264                 }
265                 i++;
266             }
267         if (!groupExists) {
268             documentElement = doc.createElement("group");
269             documentElement.setAttribute("name", ui.group_name->currentText());
270             documentElement.setAttribute("renderer", "avformat");
271             doc.documentElement().appendChild(documentElement);
272         }
273         QDomElement profileElement = doc.createElement("profile");
274         profileElement.setAttribute("name", newProfileName);
275         profileElement.setAttribute("extension", ui.extension->text().simplified());
276         profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
277         documentElement.appendChild(profileElement);
278
279         //QCString save = doc.toString().utf8();
280
281         if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
282             KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
283             delete d;
284             return;
285         }
286         QTextStream out(&file);
287         out << doc.toString();
288         file.close();
289         parseProfiles(newGroupName, newProfileName);
290     }
291     delete d;
292 }
293
294 void RenderWidget::slotDeleteProfile() {
295     QString currentGroup = m_view.format_list->currentItem()->text();
296     QString currentProfile = m_view.size_list->currentItem()->text();
297
298     QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
299     QDomDocument doc;
300     QFile file(exportFile);
301     doc.setContent(&file, false);
302     file.close();
303
304     QDomElement documentElement;
305     bool groupExists = false;
306     QString groupName;
307     QDomNodeList groups = doc.elementsByTagName("group");
308     int i = 0;
309
310     while (!groups.item(i).isNull()) {
311         documentElement = groups.item(i).toElement();
312         groupName = documentElement.attribute("name");
313         if (groupName == currentGroup) {
314             QDomNodeList children = documentElement.childNodes();
315             for (int j = 0; j < children.count(); j++) {
316                 QDomElement pro = children.at(j).toElement();
317                 if (pro.attribute("name") == currentProfile) {
318                     groups.item(i).removeChild(children.at(j));
319                     if (groups.item(i).childNodes().isEmpty())
320                         doc.documentElement().removeChild(groups.item(i));
321                     break;
322                 }
323             }
324             break;
325         }
326         i++;
327     }
328
329     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
330         KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
331         return;
332     }
333     QTextStream out(&file);
334     out << doc.toString();
335     file.close();
336     parseProfiles(currentGroup);
337     focusFirstVisibleItem();
338 }
339
340 void RenderWidget::updateButtons() {
341     if (!m_view.size_list->currentItem() || m_view.size_list->currentItem()->isHidden()) {
342         m_view.buttonSave->setEnabled(false);
343         m_view.buttonDelete->setEnabled(false);
344         m_view.buttonEdit->setEnabled(false);
345     } else {
346         m_view.buttonSave->setEnabled(true);
347         if (m_view.size_list->currentItem()->data(EditableRole).toString().isEmpty()) {
348             m_view.buttonDelete->setEnabled(false);
349             m_view.buttonEdit->setEnabled(false);
350         } else {
351             m_view.buttonDelete->setEnabled(true);
352             m_view.buttonEdit->setEnabled(true);
353         }
354     }
355 }
356
357
358 void RenderWidget::focusFirstVisibleItem() {
359     if (m_view.size_list->currentItem() && !m_view.size_list->currentItem()->isHidden()) {
360         updateButtons();
361         return;
362     }
363     for (uint ix = 0; ix < m_view.size_list->count(); ix++) {
364         QListWidgetItem *item = m_view.size_list->item(ix);
365         if (item && !item->isHidden()) {
366             m_view.size_list->setCurrentRow(ix);
367             break;
368         }
369     }
370     if (!m_view.size_list->currentItem()) m_view.size_list->setCurrentRow(0);
371     updateButtons();
372 }
373
374 void RenderWidget::slotExport() {
375     QListWidgetItem *item = m_view.size_list->currentItem();
376     if (!item) return;
377     QFile f(m_view.out_file->url().path());
378     if (f.exists()) {
379         if (KMessageBox::warningYesNo(this, i18n("File already exists. Do you want to overwrite it ?")) != KMessageBox::Yes)
380             return;
381     }
382     QStringList overlayargs;
383     if (m_view.tc_overlay->isChecked()) {
384         QString filterFile = KStandardDirs::locate("appdata", "metadata.properties");
385         overlayargs << "meta.attr.timecode=1" << "meta.attr.timecode.markup=#timecode";
386         overlayargs << "-attach" << "data_feed:attr_check" << "-attach";
387         overlayargs << "data_show:" + filterFile << "_fezzik=1" << "dynamic=1";
388     }
389     double startPos = -1;
390     double endPos = -1;
391     if (m_view.render_guide->isChecked()) {
392         startPos = m_view.guide_start->itemData(m_view.guide_start->currentIndex()).toDouble();
393         endPos = m_view.guide_end->itemData(m_view.guide_end->currentIndex()).toDouble();
394     }
395     QString renderArgs = m_view.advanced_params->toPlainText();
396     renderArgs.replace("%width", QString::number(m_profile.width));
397     renderArgs.replace("%height", QString::number(m_profile.height));
398     renderArgs.replace("%dar", "@" + QString::number(m_profile.display_aspect_num) + "/" + QString::number(m_profile.display_aspect_den));
399     if (m_view.force_progressive->checkState() == Qt::Checked) renderArgs.append(" progressive=1");
400     else if (m_view.force_progressive->checkState() == Qt::Unchecked) renderArgs.append(" progressive=0");
401     emit doRender(m_view.out_file->url().path(), item->data(RenderRole).toString(), overlayargs, renderArgs.simplified().split(' '), m_view.render_zone->isChecked(), m_view.play_after->isChecked(), startPos, endPos);
402 }
403
404 void RenderWidget::setProfile(MltVideoProfile profile) {
405     m_profile = profile;
406     //WARNING: this way to tell the video standard is a bit hackish...
407     if (m_profile.description.contains("pal", Qt::CaseInsensitive) || m_profile.description.contains("25", Qt::CaseInsensitive) || m_profile.description.contains("50", Qt::CaseInsensitive)) m_view.format_selection->setCurrentIndex(0);
408     else m_view.format_selection->setCurrentIndex(1);
409     m_view.force_progressive->setCheckState(Qt::PartiallyChecked);
410     refreshView();
411 }
412
413 void RenderWidget::refreshView() {
414     m_view.size_list->blockSignals(true);
415     QListWidgetItem *item = m_view.format_list->currentItem();
416     if (!item) {
417         m_view.format_list->setCurrentRow(0);
418         item = m_view.format_list->currentItem();
419     }
420     if (!item) return;
421     QString std;
422     QString group = item->text();
423     QListWidgetItem *sizeItem;
424     bool firstSelected = false;
425     for (int i = 0; i < m_view.size_list->count(); i++) {
426         sizeItem = m_view.size_list->item(i);
427         if (sizeItem->data(GroupRole) == group) {
428             std = sizeItem->data(StandardRole).toString();
429             if (!std.isEmpty()) {
430                 if (std.contains("PAL", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 0);
431                 else if (std.contains("NTSC", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 1);
432             } else {
433                 sizeItem->setHidden(false);
434                 if (!firstSelected) m_view.size_list->setCurrentItem(sizeItem);
435                 firstSelected = true;
436             }
437             if (!KdenliveSettings::experimentalrender() && !sizeItem->isHidden()) {
438                 // hide experimental codecs (which do resize the video)
439                 std = sizeItem->data(ParamsRole).toString();
440                 if (std.contains(" s=")) {
441                     QString subsize = std.section(" s=", 1, 1);
442                     subsize = subsize.section(' ', 0, 0).toLower();
443                     if (subsize != "%widthx%height") {
444                         const QString currentSize = QString::number(m_profile.width) + 'x' + QString::number(m_profile.height);
445                         if (subsize != currentSize) sizeItem->setHidden(true);
446                     }
447                 }
448             }
449         } else sizeItem->setHidden(true);
450     }
451     focusFirstVisibleItem();
452     m_view.size_list->blockSignals(false);
453     refreshParams();
454 }
455
456 void RenderWidget::refreshParams() {
457     QListWidgetItem *item = m_view.size_list->currentItem();
458     if (!item || item->isHidden()) {
459         m_view.advanced_params->clear();
460         m_view.buttonStart->setEnabled(false);
461         return;
462     }
463     QString params = item->data(ParamsRole).toString();
464     QString extension = item->data(ExtensionRole).toString();
465     m_view.advanced_params->setPlainText(params);
466     m_view.advanced_params->setToolTip(params);
467     KUrl url = m_view.out_file->url();
468     if (!url.isEmpty()) {
469         QString path = url.path();
470         int pos = path.lastIndexOf('.') + 1;
471         if (pos == 0) path.append('.') + extension;
472         else path = path.left(pos) + extension;
473         m_view.out_file->setUrl(KUrl(path));
474     } else {
475         m_view.out_file->setUrl(KUrl(QDir::homePath() + "/untitled." + extension));
476     }
477
478     if (item->data(EditableRole).toString().isEmpty()) {
479         m_view.buttonDelete->setEnabled(false);
480         m_view.buttonEdit->setEnabled(false);
481     } else {
482         m_view.buttonDelete->setEnabled(true);
483         m_view.buttonEdit->setEnabled(true);
484     }
485     m_view.buttonStart->setEnabled(true);
486 }
487
488 void RenderWidget::parseProfiles(QString group, QString profile) {
489     m_view.size_list->clear();
490     m_view.format_list->clear();
491     QString exportFile = KStandardDirs::locate("appdata", "export/profiles.xml");
492     parseFile(exportFile, false);
493     exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
494     if (QFile::exists(exportFile)) parseFile(exportFile, true);
495     refreshView();
496     QList<QListWidgetItem *> child;
497     child = m_view.format_list->findItems(group, Qt::MatchExactly);
498     if (!child.isEmpty()) m_view.format_list->setCurrentItem(child.at(0));
499     child = m_view.size_list->findItems(profile, Qt::MatchExactly);
500     if (!child.isEmpty()) m_view.size_list->setCurrentItem(child.at(0));
501 }
502
503 void RenderWidget::parseFile(QString exportFile, bool editable) {
504     QDomDocument doc;
505     QFile file(exportFile);
506     doc.setContent(&file, false);
507     file.close();
508     QDomElement documentElement;
509     QDomElement profileElement;
510     QDomNodeList groups = doc.elementsByTagName("group");
511
512     if (groups.count() == 0) {
513         kDebug() << "// Export file: " << exportFile << " IS BROKEN";
514         return;
515     }
516
517     int i = 0;
518     QString groupName;
519     QString profileName;
520     QString extension;
521     QString prof_extension;
522     QString renderer;
523     QString params;
524     QString standard;
525     QListWidgetItem *item;
526     while (!groups.item(i).isNull()) {
527         documentElement = groups.item(i).toElement();
528         groupName = documentElement.attribute("name", QString::null);
529         extension = documentElement.attribute("extension", QString::null);
530         renderer = documentElement.attribute("renderer", QString::null);
531         if (m_view.format_list->findItems(groupName, Qt::MatchExactly).isEmpty())
532             new QListWidgetItem(groupName, m_view.format_list);
533
534         QDomNode n = groups.item(i).firstChild();
535         while (!n.isNull()) {
536             profileElement = n.toElement();
537             profileName = profileElement.attribute("name");
538             standard = profileElement.attribute("standard");
539             params = profileElement.attribute("args");
540             prof_extension = profileElement.attribute("extension");
541             if (!prof_extension.isEmpty()) extension = prof_extension;
542             item = new QListWidgetItem(profileName, m_view.size_list);
543             item->setData(GroupRole, groupName);
544             item->setData(ExtensionRole, extension);
545             item->setData(RenderRole, renderer);
546             item->setData(StandardRole, standard);
547             item->setData(ParamsRole, params);
548             if (editable) item->setData(EditableRole, "true");
549             n = n.nextSibling();
550         }
551
552         i++;
553     }
554 }
555
556
557
558 #include "renderwidget.moc"
559
560