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 ***************************************************************************/
21 #include "renderwidget.h"
22 #include "kdenlivesettings.h"
23 #include "ui_saveprofile_ui.h"
26 #include <KStandardDirs>
28 #include <KMessageBox>
31 #include <KIO/NetAccess>
32 #include <KColorScheme>
33 #include <KNotification>
34 #include <KStartupInfo>
35 // #include <knewstuff2/engine.h>
37 #include <QDomDocument>
38 #include <QItemDelegate>
39 #include <QTreeWidgetItem>
40 #include <QListWidgetItem>
41 #include <QHeaderView>
43 #include <QInputDialog>
45 #include <QDBusConnectionInterface>
46 #include <QDBusInterface>
48 const int GroupRole = Qt::UserRole;
49 const int ExtensionRole = GroupRole + 1;
50 const int StandardRole = GroupRole + 2;
51 const int RenderRole = GroupRole + 3;
52 const int ParamsRole = GroupRole + 4;
53 const int EditableRole = GroupRole + 5;
54 const int MetaGroupRole = GroupRole + 6;
55 const int ExtraRole = GroupRole + 7;
58 const int WAITINGJOB = 0;
59 const int RUNNINGJOB = 1;
60 const int FINISHEDJOB = 2;
63 RenderWidget::RenderWidget(const QString &projectfolder, QWidget * parent) :
65 m_projectFolder(projectfolder),
66 m_blockProcessing(false)
69 setWindowTitle(i18n("Rendering"));
70 m_view.buttonDelete->setIcon(KIcon("trash-empty"));
71 m_view.buttonDelete->setToolTip(i18n("Delete profile"));
72 m_view.buttonDelete->setEnabled(false);
74 m_view.buttonEdit->setIcon(KIcon("document-properties"));
75 m_view.buttonEdit->setToolTip(i18n("Edit profile"));
76 m_view.buttonEdit->setEnabled(false);
78 m_view.buttonSave->setIcon(KIcon("document-new"));
79 m_view.buttonSave->setToolTip(i18n("Create new profile"));
81 m_view.buttonInfo->setIcon(KIcon("help-about"));
82 m_view.hide_log->setIcon(KIcon("go-down"));
84 if (KdenliveSettings::showrenderparams()) {
85 m_view.buttonInfo->setDown(true);
86 } else m_view.advanced_params->hide();
88 m_view.rescale_size->setInputMask("0099\\x0099");
89 m_view.rescale_size->setText("320x240");
92 QMenu *renderMenu = new QMenu(i18n("Start Rendering"), this);
93 QAction *renderAction = renderMenu->addAction(KIcon("video-x-generic"), i18n("Render to File"));
94 connect(renderAction, SIGNAL(triggered()), this, SLOT(slotPrepareExport()));
95 QAction *scriptAction = renderMenu->addAction(KIcon("application-x-shellscript"), i18n("Generate Script"));
96 connect(scriptAction, SIGNAL(triggered()), this, SLOT(slotGenerateScript()));
98 m_view.buttonStart->setMenu(renderMenu);
99 m_view.buttonStart->setPopupMode(QToolButton::MenuButtonPopup);
100 m_view.buttonStart->setDefaultAction(renderAction);
101 m_view.buttonStart->setToolButtonStyle(Qt::ToolButtonTextOnly);
102 m_view.abort_job->setEnabled(false);
103 m_view.start_script->setEnabled(false);
104 m_view.delete_script->setEnabled(false);
106 m_view.format_list->setAlternatingRowColors(true);
107 m_view.size_list->setAlternatingRowColors(true);
112 connect(m_view.start_script, SIGNAL(clicked()), this, SLOT(slotStartScript()));
113 connect(m_view.delete_script, SIGNAL(clicked()), this, SLOT(slotDeleteScript()));
114 connect(m_view.scripts_list, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckScript()));
115 connect(m_view.running_jobs, SIGNAL(itemSelectionChanged()), this, SLOT(slotCheckJob()));
116 connect(m_view.running_jobs, SIGNAL(itemDoubleClicked(QTreeWidgetItem *, int)), this, SLOT(slotPlayRendering(QTreeWidgetItem *, int)));
118 connect(m_view.buttonInfo, SIGNAL(clicked()), this, SLOT(showInfoPanel()));
120 connect(m_view.buttonSave, SIGNAL(clicked()), this, SLOT(slotSaveProfile()));
121 connect(m_view.buttonEdit, SIGNAL(clicked()), this, SLOT(slotEditProfile()));
122 connect(m_view.buttonDelete, SIGNAL(clicked()), this, SLOT(slotDeleteProfile()));
123 connect(m_view.abort_job, SIGNAL(clicked()), this, SLOT(slotAbortCurrentJob()));
124 connect(m_view.clean_up, SIGNAL(clicked()), this, SLOT(slotCLeanUpJobs()));
125 connect(m_view.hide_log, SIGNAL(clicked()), this, SLOT(slotHideLog()));
127 connect(m_view.buttonClose, SIGNAL(clicked()), this, SLOT(hide()));
128 connect(m_view.buttonClose2, SIGNAL(clicked()), this, SLOT(hide()));
129 connect(m_view.buttonClose3, SIGNAL(clicked()), this, SLOT(hide()));
130 connect(m_view.rescale, SIGNAL(toggled(bool)), m_view.rescale_size, SLOT(setEnabled(bool)));
131 connect(m_view.destination_list, SIGNAL(activated(int)), this, SLOT(refreshView()));
132 connect(m_view.out_file, SIGNAL(textChanged(const QString &)), this, SLOT(slotUpdateButtons()));
133 connect(m_view.out_file, SIGNAL(urlSelected(const KUrl &)), this, SLOT(slotUpdateButtons(const KUrl &)));
134 connect(m_view.format_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshView()));
135 connect(m_view.size_list, SIGNAL(currentRowChanged(int)), this, SLOT(refreshParams()));
137 connect(m_view.size_list, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this, SLOT(slotEditItem(QListWidgetItem *)));
139 connect(m_view.render_guide, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
140 connect(m_view.render_zone, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
141 connect(m_view.render_full, SIGNAL(clicked(bool)), this, SLOT(slotUpdateGuideBox()));
143 connect(m_view.guide_end, SIGNAL(activated(int)), this, SLOT(slotCheckStartGuidePosition()));
144 connect(m_view.guide_start, SIGNAL(activated(int)), this, SLOT(slotCheckEndGuidePosition()));
146 connect(m_view.format_selection, SIGNAL(activated(int)), this, SLOT(refreshView()));
148 m_view.buttonStart->setEnabled(false);
149 m_view.rescale_size->setEnabled(false);
150 m_view.guides_box->setVisible(false);
151 m_view.open_dvd->setVisible(false);
152 m_view.create_chapter->setVisible(false);
153 m_view.open_browser->setVisible(false);
154 m_view.error_box->setVisible(false);
156 m_view.splitter->setStretchFactor(1, 5);
157 m_view.splitter->setStretchFactor(0, 2);
159 m_view.out_file->setMode(KFile::File);
161 m_view.running_jobs->setHeaderLabels(QStringList() << QString() << i18n("File") << i18n("Progress"));
162 m_jobsDelegate = new RenderViewDelegate(this);
163 m_view.running_jobs->setItemDelegate(m_jobsDelegate);
165 QHeaderView *header = m_view.running_jobs->header();
166 QFontMetrics fm = fontMetrics();
167 header->setResizeMode(0, QHeaderView::Fixed);
168 header->resizeSection(0, 30);
169 header->setResizeMode(1, QHeaderView::Interactive);
170 header->setResizeMode(2, QHeaderView::Fixed);
171 header->resizeSection(1, width() * 2 / 3 - 15);
172 header->setResizeMode(2, QHeaderView::Interactive);
173 //header->setResizeMode(1, QHeaderView::Fixed);
176 m_view.scripts_list->setHeaderLabels(QStringList() << QString() << i18n("Script Files"));
177 m_scriptsDelegate = new RenderViewDelegate(this);
178 m_view.scripts_list->setItemDelegate(m_scriptsDelegate);
179 header = m_view.scripts_list->header();
180 header->setResizeMode(0, QHeaderView::Fixed);
181 header->resizeSection(0, 30);
183 // Find path for Kdenlive renderer
184 m_renderer = QCoreApplication::applicationDirPath() + QString("/kdenlive_render");
185 if (!QFile::exists(m_renderer)) {
186 m_renderer = KStandardDirs::findExe("kdenlive_render");
187 if (m_renderer.isEmpty()) m_renderer = KStandardDirs::locate("exe", "kdenlive_render");
188 if (m_renderer.isEmpty()) m_renderer = "kdenlive_render";
191 QDBusConnectionInterface* interface = QDBusConnection::sessionBus().interface();
192 if (!interface || !interface->isServiceRegistered("org.kde.ksmserver")) {
193 m_view.shutdown->setEnabled(false);
196 focusFirstVisibleItem();
199 RenderWidget::~RenderWidget()
201 m_view.running_jobs->blockSignals(true);
202 m_view.scripts_list->blockSignals(true);
203 m_view.running_jobs->clear();
204 m_view.scripts_list->clear();
205 delete m_jobsDelegate;
206 delete m_scriptsDelegate;
209 void RenderWidget::slotEditItem(QListWidgetItem *item)
211 QString edit = item->data(EditableRole).toString();
212 if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) slotSaveProfile();
213 else slotEditProfile();
216 void RenderWidget::showInfoPanel()
218 if (m_view.advanced_params->isVisible()) {
219 m_view.advanced_params->setVisible(false);
220 m_view.buttonInfo->setDown(false);
221 KdenliveSettings::setShowrenderparams(false);
223 m_view.advanced_params->setVisible(true);
224 m_view.buttonInfo->setDown(true);
225 KdenliveSettings::setShowrenderparams(true);
229 void RenderWidget::setDocumentPath(const QString path)
231 if (m_view.out_file->url().directory() == KUrl(m_projectFolder).directory()) {
232 const QString fileName = m_view.out_file->url().fileName();
233 m_view.out_file->setUrl(KUrl(path + fileName));
235 m_projectFolder = path;
240 void RenderWidget::slotUpdateGuideBox()
242 m_view.guides_box->setVisible(m_view.render_guide->isChecked());
245 void RenderWidget::slotCheckStartGuidePosition()
247 if (m_view.guide_start->currentIndex() > m_view.guide_end->currentIndex())
248 m_view.guide_start->setCurrentIndex(m_view.guide_end->currentIndex());
251 void RenderWidget::slotCheckEndGuidePosition()
253 if (m_view.guide_end->currentIndex() < m_view.guide_start->currentIndex())
254 m_view.guide_end->setCurrentIndex(m_view.guide_start->currentIndex());
257 void RenderWidget::setGuides(QDomElement guidesxml, double duration)
259 m_view.guide_start->clear();
260 m_view.guide_end->clear();
261 QDomNodeList nodes = guidesxml.elementsByTagName("guide");
262 if (nodes.count() > 0) {
263 m_view.guide_start->addItem(i18n("Beginning"), "0");
264 m_view.render_guide->setEnabled(true);
265 m_view.create_chapter->setEnabled(true);
267 m_view.render_guide->setEnabled(false);
268 m_view.create_chapter->setEnabled(false);
270 double fps = (double) m_profile.frame_rate_num / m_profile.frame_rate_den;
271 for (int i = 0; i < nodes.count(); i++) {
272 QDomElement e = nodes.item(i).toElement();
274 GenTime pos = GenTime(e.attribute("time").toDouble());
275 const QString guidePos = Timecode::getStringTimecode(pos.frames(fps), fps);
276 m_view.guide_start->addItem(e.attribute("comment") + '/' + guidePos, e.attribute("time").toDouble());
277 m_view.guide_end->addItem(e.attribute("comment") + '/' + guidePos, e.attribute("time").toDouble());
280 if (nodes.count() > 0)
281 m_view.guide_end->addItem(i18n("End"), QString::number(duration));
285 * Will be called when the user selects an output file via the file dialog.
286 * File extension will be added automatically.
288 void RenderWidget::slotUpdateButtons(KUrl url)
290 if (m_view.out_file->url().isEmpty()) m_view.buttonStart->setEnabled(false);
292 updateButtons(); // This also checks whether the selected format is available
293 //m_view.buttonStart->setEnabled(true);
296 QListWidgetItem *item = m_view.size_list->currentItem();
297 QString extension = item->data(ExtensionRole).toString();
298 url = filenameWithExtension(url, extension);
299 m_view.out_file->setUrl(url);
304 * Will be called when the user changes the output file path in the text line.
305 * File extension must NOT be added, would make editing impossible!
307 void RenderWidget::slotUpdateButtons()
309 if (m_view.out_file->url().isEmpty()) m_view.buttonStart->setEnabled(false);
310 else updateButtons(); // This also checks whether the selected format is available
311 //else m_view.buttonStart->setEnabled(true);
314 void RenderWidget::slotSaveProfile()
316 //TODO: update to correctly use metagroups
317 Ui::SaveProfile_UI ui;
318 QDialog *d = new QDialog(this);
321 for (int i = 0; i < m_view.destination_list->count(); i++)
322 ui.destination_list->addItem(m_view.destination_list->itemIcon(i), m_view.destination_list->itemText(i), m_view.destination_list->itemData(i, Qt::UserRole));
324 ui.destination_list->setCurrentIndex(m_view.destination_list->currentIndex());
325 QString dest = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
327 QString customGroup = m_view.format_list->currentItem()->text();
328 if (customGroup.isEmpty()) customGroup = i18n("Custom");
329 ui.group_name->setText(customGroup);
331 ui.parameters->setText(m_view.advanced_params->toPlainText());
332 ui.extension->setText(m_view.size_list->currentItem()->data(ExtensionRole).toString());
333 ui.profile_name->setFocus();
335 if (d->exec() == QDialog::Accepted && !ui.profile_name->text().simplified().isEmpty()) {
336 QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
338 QFile file(exportFile);
339 doc.setContent(&file, false);
341 QDomElement documentElement;
342 QDomElement profiles = doc.documentElement();
343 if (profiles.isNull() || profiles.tagName() != "profiles") {
345 profiles = doc.createElement("profiles");
346 profiles.setAttribute("version", 1);
347 doc.appendChild(profiles);
349 int version = profiles.attribute("version", 0).toInt();
351 kDebug() << "// OLD profile version";
353 profiles = doc.createElement("profiles");
354 profiles.setAttribute("version", 1);
355 doc.appendChild(profiles);
358 QString newProfileName = ui.profile_name->text().simplified();
359 QString newGroupName = ui.group_name->text().simplified();
360 if (newGroupName.isEmpty()) newGroupName = i18n("Custom");
361 QString newMetaGroupId = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
362 QDomNodeList profilelist = doc.elementsByTagName("profile");
364 while (!profilelist.item(i).isNull()) {
365 // make sure a profile with same name doesn't exist
366 documentElement = profilelist.item(i).toElement();
367 QString profileName = documentElement.attribute("name");
368 if (profileName == newProfileName) {
369 // a profile with that same name already exists
371 newProfileName = QInputDialog::getText(this, i18n("Profile already exists"), i18n("This profile name already exists. Change the name if you don't want to overwrite it."), QLineEdit::Normal, newProfileName, &ok);
373 if (profileName == newProfileName) {
374 profiles.removeChild(profilelist.item(i));
381 QDomElement profileElement = doc.createElement("profile");
382 profileElement.setAttribute("name", newProfileName);
383 profileElement.setAttribute("category", newGroupName);
384 profileElement.setAttribute("destinationid", newMetaGroupId);
385 profileElement.setAttribute("extension", ui.extension->text().simplified());
386 profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
387 profiles.appendChild(profileElement);
389 //QCString save = doc.toString().utf8();
391 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
392 KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
396 QTextStream out(&file);
397 out << doc.toString();
398 if (file.error() != QFile::NoError) {
399 KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
405 parseProfiles(newMetaGroupId, newGroupName, newProfileName);
410 void RenderWidget::slotEditProfile()
412 QListWidgetItem *item = m_view.size_list->currentItem();
414 QString currentGroup = m_view.format_list->currentItem()->text();
416 QString params = item->data(ParamsRole).toString();
417 QString extension = item->data(ExtensionRole).toString();
418 QString currentProfile = item->text();
420 Ui::SaveProfile_UI ui;
421 QDialog *d = new QDialog(this);
424 for (int i = 0; i < m_view.destination_list->count(); i++)
425 ui.destination_list->addItem(m_view.destination_list->itemIcon(i), m_view.destination_list->itemText(i), m_view.destination_list->itemData(i, Qt::UserRole));
427 ui.destination_list->setCurrentIndex(m_view.destination_list->currentIndex());
428 QString dest = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
430 QString customGroup = m_view.format_list->currentItem()->text();
431 if (customGroup.isEmpty()) customGroup = i18n("Custom");
432 ui.group_name->setText(customGroup);
434 ui.profile_name->setText(currentProfile);
435 ui.extension->setText(extension);
436 ui.parameters->setText(params);
437 ui.profile_name->setFocus();
438 d->setWindowTitle(i18n("Edit Profile"));
439 if (d->exec() == QDialog::Accepted) {
440 slotDeleteProfile(false);
441 QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
443 QFile file(exportFile);
444 doc.setContent(&file, false);
446 QDomElement documentElement;
447 QDomElement profiles = doc.documentElement();
449 if (profiles.isNull() || profiles.tagName() != "profiles") {
451 profiles = doc.createElement("profiles");
452 profiles.setAttribute("version", 1);
453 doc.appendChild(profiles);
456 int version = profiles.attribute("version", 0).toInt();
458 kDebug() << "// OLD profile version";
460 profiles = doc.createElement("profiles");
461 profiles.setAttribute("version", 1);
462 doc.appendChild(profiles);
465 QString newProfileName = ui.profile_name->text().simplified();
466 QString newGroupName = ui.group_name->text().simplified();
467 if (newGroupName.isEmpty()) newGroupName = i18n("Custom");
468 QString newMetaGroupId = ui.destination_list->itemData(ui.destination_list->currentIndex(), Qt::UserRole).toString();
469 QDomNodeList profilelist = doc.elementsByTagName("profile");
471 while (!profilelist.item(i).isNull()) {
472 // make sure a profile with same name doesn't exist
473 documentElement = profilelist.item(i).toElement();
474 QString profileName = documentElement.attribute("name");
475 if (profileName == newProfileName) {
476 // a profile with that same name already exists
478 newProfileName = QInputDialog::getText(this, i18n("Profile already exists"), i18n("This profile name already exists. Change the name if you don't want to overwrite it."), QLineEdit::Normal, newProfileName, &ok);
480 if (profileName == newProfileName) {
481 profiles.removeChild(profilelist.item(i));
488 QDomElement profileElement = doc.createElement("profile");
489 profileElement.setAttribute("name", newProfileName);
490 profileElement.setAttribute("category", newGroupName);
491 profileElement.setAttribute("destinationid", newMetaGroupId);
492 profileElement.setAttribute("extension", ui.extension->text().simplified());
493 profileElement.setAttribute("args", ui.parameters->toPlainText().simplified());
494 profiles.appendChild(profileElement);
496 //QCString save = doc.toString().utf8();
498 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
499 KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
502 QTextStream out(&file);
503 out << doc.toString();
504 if (file.error() != QFile::NoError) {
505 KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
510 parseProfiles(newMetaGroupId, newGroupName, newProfileName);
514 void RenderWidget::slotDeleteProfile(bool refresh)
516 //TODO: delete a profile installed by KNewStuff the easy way
518 QString edit = m_view.size_list->currentItem()->data(EditableRole).toString();
519 if (!edit.endsWith("customprofiles.xml")) {
520 // This is a KNewStuff installed file, process through KNS
521 KNS::Engine engine(0);
522 if (engine.init("kdenlive_render.knsrc")) {
523 KNS::Entry::List entries;
527 QString currentGroup = m_view.format_list->currentItem()->text();
528 QString currentProfile = m_view.size_list->currentItem()->text();
529 QString metaGroupId = m_view.destination_list->itemData(m_view.destination_list->currentIndex(), Qt::UserRole).toString();
531 QString exportFile = KStandardDirs::locateLocal("appdata", "export/customprofiles.xml");
533 QFile file(exportFile);
534 doc.setContent(&file, false);
537 QDomElement documentElement;
538 QDomNodeList profiles = doc.elementsByTagName("profile");
544 while (!profiles.item(i).isNull()) {
545 documentElement = profiles.item(i).toElement();
546 profileName = documentElement.attribute("name");
547 groupName = documentElement.attribute("category");
548 destination = documentElement.attribute("destinationid");
550 if (profileName == currentProfile && groupName == currentGroup && destination == metaGroupId) {
551 kDebug() << "// GOT it: " << profileName;
552 doc.documentElement().removeChild(profiles.item(i));
558 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
559 KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
562 QTextStream out(&file);
563 out << doc.toString();
564 if (file.error() != QFile::NoError) {
565 KMessageBox::error(this, i18n("Cannot write to file %1", exportFile));
571 parseProfiles(metaGroupId, currentGroup);
572 focusFirstVisibleItem();
576 void RenderWidget::updateButtons()
578 if (!m_view.size_list->currentItem() || m_view.size_list->currentItem()->isHidden()) {
579 m_view.buttonSave->setEnabled(false);
580 m_view.buttonDelete->setEnabled(false);
581 m_view.buttonEdit->setEnabled(false);
582 m_view.buttonStart->setEnabled(false);
584 m_view.buttonSave->setEnabled(true);
585 m_view.buttonStart->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
586 QString edit = m_view.size_list->currentItem()->data(EditableRole).toString();
587 if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) {
588 m_view.buttonDelete->setEnabled(false);
589 m_view.buttonEdit->setEnabled(false);
591 m_view.buttonDelete->setEnabled(true);
592 m_view.buttonEdit->setEnabled(true);
598 void RenderWidget::focusFirstVisibleItem()
600 if (m_view.size_list->currentItem() && !m_view.size_list->currentItem()->isHidden()) {
604 for (int ix = 0; ix < m_view.size_list->count(); ix++) {
605 QListWidgetItem *item = m_view.size_list->item(ix);
606 if (item && !item->isHidden()) {
607 m_view.size_list->setCurrentRow(ix);
611 if (!m_view.size_list->currentItem()) m_view.size_list->setCurrentRow(0);
615 void RenderWidget::slotPrepareExport(bool scriptExport)
617 if (!QFile::exists(KdenliveSettings::rendererpath())) {
618 KMessageBox::sorry(this, i18n("Cannot find the melt program required for rendering (part of Mlt)"));
621 if (m_view.play_after->isChecked() && KdenliveSettings::defaultplayerapp().isEmpty())
622 KMessageBox::sorry(this, i18n("Cannot play video after rendering because the default video player application is not set.\nPlease define it in Kdenlive settings dialog."));
624 if (m_view.create_chapter->isChecked()) chapterFile = m_view.out_file->url().path() + ".dvdchapter";
627 KStandardDirs::makeDir(m_view.out_file->url().directory());
629 emit prepareRenderingData(scriptExport, m_view.render_zone->isChecked(), chapterFile);
633 void RenderWidget::slotExport(bool scriptExport, int zoneIn, int zoneOut, const QString &playlistPath, const QString &scriptPath)
635 QListWidgetItem *item = m_view.size_list->currentItem();
638 QString dest = m_view.out_file->url().path();
639 if (dest.isEmpty()) return;
641 // Check whether target file has an extension.
642 // If not, ask whether extension should be added or not.
643 QString extension = item->data(ExtensionRole).toString();
644 if (!dest.endsWith(extension, Qt::CaseInsensitive)) {
645 if (KMessageBox::questionYesNo(this, i18n("File has no extension. Add extension (%1)?", extension)) == KMessageBox::Yes) {
646 dest.append("." + extension);
652 if (KMessageBox::warningYesNo(this, i18n("Output file already exists. Do you want to overwrite it?")) != KMessageBox::Yes)
656 QStringList overlayargs;
657 if (m_view.tc_overlay->isChecked()) {
658 QString filterFile = KStandardDirs::locate("appdata", "metadata.properties");
659 overlayargs << "meta.attr.timecode=1" << "meta.attr.timecode.markup=#timecode";
660 overlayargs << "-attach" << "data_feed:attr_check" << "-attach";
661 overlayargs << "data_show:" + filterFile << "_loader=1" << "dynamic=1";
664 QStringList render_process_args;
666 if (!scriptExport) render_process_args << "-erase";
667 if (KdenliveSettings::usekuiserver()) render_process_args << "-kuiserver";
669 double guideStart = 0;
672 if (m_view.render_zone->isChecked()) render_process_args << "in=" + QString::number(zoneIn) << "out=" + QString::number(zoneOut);
673 else if (m_view.render_guide->isChecked()) {
674 double fps = (double) m_profile.frame_rate_num / m_profile.frame_rate_den;
675 guideStart = m_view.guide_start->itemData(m_view.guide_start->currentIndex()).toDouble();
676 guideEnd = m_view.guide_end->itemData(m_view.guide_end->currentIndex()).toDouble();
677 render_process_args << "in=" + QString::number(GenTime(guideStart).frames(fps)) << "out=" + QString::number(GenTime(guideEnd).frames(fps));
680 if (!overlayargs.isEmpty()) render_process_args << "preargs=" + overlayargs.join(" ");
682 render_process_args << KdenliveSettings::rendererpath() << m_profile.path << item->data(RenderRole).toString();
683 if (m_view.play_after->isChecked()) render_process_args << KdenliveSettings::KdenliveSettings::defaultplayerapp();
684 else render_process_args << "-";
686 QString renderArgs = m_view.advanced_params->toPlainText().simplified();
688 // Adjust frame scale
691 if (m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
692 width = m_view.rescale_size->text().section('x', 0, 0).toInt();
693 height = m_view.rescale_size->text().section('x', 1, 1).toInt();
695 width = m_profile.width;
696 height = m_profile.height;
698 renderArgs.replace("%dar", '@' + QString::number(m_profile.display_aspect_num) + '/' + QString::number(m_profile.display_aspect_den));
701 if (m_view.scanning_list->currentIndex() == 1) renderArgs.append(" progressive=1");
702 else if (m_view.scanning_list->currentIndex() == 2) renderArgs.append(" progressive=0");
704 // disable audio if requested
705 if (!m_view.export_audio->isChecked())
706 renderArgs.append(" an=1 ");
708 // Check if the rendering profile is different from project profile,
709 // in which case we need to use the producer_comsumer from MLT
710 QString std = renderArgs;
711 QString destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
712 const QString currentSize = QString::number(width) + 'x' + QString::number(height);
713 QString subsize = currentSize;
714 if (std.startsWith("s=")) {
715 subsize = std.section(' ', 0, 0).toLower();
716 subsize = subsize.section("=", 1, 1);
717 } else if (std.contains(" s=")) {
718 subsize = std.section(" s=", 1, 1);
719 subsize = subsize.section(' ', 0, 0).toLower();
720 } else if (destination != "audioonly" && m_view.rescale->isChecked() && m_view.rescale->isEnabled()) {
721 subsize = QString(" s=%1x%2").arg(width).arg(height);
722 // Add current size parameter
723 renderArgs.append(subsize);
725 bool resizeProfile = (subsize != currentSize);
726 QStringList paramsList = renderArgs.split(" ", QString::SkipEmptyParts);
727 for (int i = 0; i < paramsList.count(); i++) {
728 if (paramsList.at(i).startsWith("profile=")) {
729 if (paramsList.at(i).section('=', 1) != m_profile.path) resizeProfile = true;
734 if (resizeProfile) render_process_args << "consumer:" + playlistPath;
735 else render_process_args << playlistPath;
736 render_process_args << dest;
737 render_process_args << paramsList;
739 QString group = m_view.size_list->currentItem()->data(MetaGroupRole).toString();
741 QStringList renderParameters;
742 renderParameters << dest << item->data(RenderRole).toString() << renderArgs.simplified();
743 renderParameters << QString::number(zoneIn) << QString::number(zoneOut) << QString::number(m_view.play_after->isChecked());
744 renderParameters << QString::number(guideStart) << QString::number(guideEnd) << QString::number(resizeProfile);
748 // Generate script file
749 QFile file(scriptPath);
750 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
751 KMessageBox::error(this, i18n("Cannot write to file %1", scriptPath));
754 QTextStream outStream(&file);
755 outStream << "#! /bin/sh" << "\n" << "\n";
756 outStream << "SOURCE=" << "\"" + playlistPath + "\"" << "\n";
757 outStream << "TARGET=" << "\"" + dest + "\"" << "\n";
758 outStream << "RENDERER=" << "\"" + m_renderer + "\"" << "\n";
759 outStream << "MELT=" << "\"" + render_process_args.takeFirst() + "\"" << "\n";
760 outStream << "PARAMETERS=" << "\"" + render_process_args.join(" ") + "\"" << "\n";
761 outStream << "$RENDERER $MELT $PARAMETERS" << "\n" << "\n";
762 if (file.error() != QFile::NoError) {
763 KMessageBox::error(this, i18n("Cannot write to file %1", scriptPath));
768 QFile::setPermissions(scriptPath, file.permissions() | QFile::ExeUser);
770 QTimer::singleShot(400, this, SLOT(parseScriptFiles()));
771 m_view.tabWidget->setCurrentIndex(2);
774 renderParameters << scriptName;
775 m_view.tabWidget->setCurrentIndex(1);
777 // Save rendering profile to document
778 emit selectedRenderProfile(m_view.size_list->currentItem()->data(MetaGroupRole).toString(), m_view.size_list->currentItem()->text(), dest);
780 // insert item in running jobs list
781 QTreeWidgetItem *renderItem;
782 QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
783 if (!existing.isEmpty()) {
784 renderItem = existing.at(0);
785 if (renderItem->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) {
786 KMessageBox::information(this, i18n("There is already a job writing file:<br><b>%1</b><br>Abort the job if you want to overwrite it...", dest), i18n("Already running"));
789 renderItem->setData(1, Qt::UserRole + 4, QString());
791 renderItem = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
793 renderItem->setData(1, Qt::UserRole + 2, WAITINGJOB);
794 renderItem->setIcon(0, KIcon("media-playback-pause"));
795 renderItem->setData(1, Qt::UserRole, i18n("Waiting..."));
796 renderItem->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
797 renderItem->setData(1, Qt::UserRole + 1, QTime::currentTime());
799 // Set rendering type
800 if (group == "dvd") {
801 if (m_view.open_dvd->isChecked()) {
802 renderItem->setData(0, Qt::UserRole, group);
803 if (renderArgs.contains("profile=")) {
804 // rendering profile contains an MLT profile, so pass it to the running jog item, useful for dvd
805 QString prof = renderArgs.section("profile=", 1, 1);
806 prof = prof.section(' ', 0, 0);
807 kDebug() << "// render profile: " << prof;
808 renderItem->setData(0, Qt::UserRole + 1, prof);
812 if (group == "websites" && m_view.open_browser->isChecked()) {
813 renderItem->setData(0, Qt::UserRole, group);
815 QString url = m_view.size_list->currentItem()->data(ExtraRole).toString();
816 renderItem->setData(0, Qt::UserRole + 1, url);
819 renderItem->setData(1, Qt::UserRole + 3, render_process_args);
823 void RenderWidget::checkRenderStatus()
825 // check if we have a job waiting to render
826 if (m_blockProcessing) return;
827 QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
829 if (item->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) return;
830 item = m_view.running_jobs->itemBelow(item);
832 item = m_view.running_jobs->topLevelItem(0);
833 bool waitingJob = false;
835 if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) {
836 item->setData(1, Qt::UserRole + 1, QTime::currentTime());
838 if (item->data(1, Qt::UserRole + 4).isNull()) {
839 // Normal render process
840 if (QProcess::startDetached(m_renderer, item->data(1, Qt::UserRole + 3).toStringList()) == false) {
841 item->setData(1, Qt::UserRole + 2, FINISHEDJOB);
842 item->setData(1, Qt::UserRole, i18n("Rendering crashed"));
843 item->setIcon(0, KIcon("dialog-close"));
844 item->setData(2, Qt::UserRole, 100);
845 } else KNotification::event("RenderStarted", i18n("Rendering <i>%1</i> started", item->text(1)), QPixmap(), this);
848 if (QProcess::startDetached(item->data(1, Qt::UserRole + 3).toString()) == false) {
849 item->setData(1, Qt::UserRole + 2, FINISHEDJOB);
850 item->setData(1, Qt::UserRole, i18n("Rendering crashed"));
851 item->setIcon(0, KIcon("dialog-close"));
852 item->setData(2, Qt::UserRole, 100);
857 item = m_view.running_jobs->itemBelow(item);
859 if (waitingJob == false && m_view.shutdown->isChecked()) emit shutdown();
862 int RenderWidget::waitingJobsCount() const
865 QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
867 if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) count++;
868 item = m_view.running_jobs->itemBelow(item);
873 void RenderWidget::setProfile(MltVideoProfile profile)
876 //WARNING: this way to tell the video standard is a bit hackish...
877 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);
878 else m_view.format_selection->setCurrentIndex(1);
879 m_view.scanning_list->setCurrentIndex(0);
883 void RenderWidget::refreshView()
885 m_view.size_list->blockSignals(true);
886 QListWidgetItem *sizeItem;
889 KIcon brokenIcon("dialog-close");
890 if (m_view.destination_list->currentIndex() > 0)
891 destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
894 if (destination == "dvd") {
895 m_view.open_dvd->setVisible(true);
896 m_view.create_chapter->setVisible(true);
898 m_view.open_dvd->setVisible(false);
899 m_view.create_chapter->setVisible(false);
901 if (destination == "websites") m_view.open_browser->setVisible(true);
902 else m_view.open_browser->setVisible(false);
903 if (!destination.isEmpty() && QString("dvd websites audioonly").contains(destination))
904 m_view.rescale->setEnabled(false);
905 else m_view.rescale->setEnabled(true);
906 // hide groups that are not in the correct destination
907 for (int i = 0; i < m_view.format_list->count(); i++) {
908 sizeItem = m_view.format_list->item(i);
909 if (sizeItem->data(MetaGroupRole).toString() == destination) {
910 sizeItem->setHidden(false);
911 //kDebug() << "// SET GRP:: " << sizeItem->text() << ", METY:" << sizeItem->data(MetaGroupRole).toString();
912 } else sizeItem->setHidden(true);
915 // activate first visible item
916 QListWidgetItem * item = m_view.format_list->currentItem();
917 if (!item || item->isHidden()) {
918 for (int i = 0; i < m_view.format_list->count(); i++) {
919 if (!m_view.format_list->item(i)->isHidden()) {
920 m_view.format_list->setCurrentRow(i);
924 item = m_view.format_list->currentItem();
926 if (!item || item->isHidden()) {
927 m_view.format_list->setEnabled(false);
928 m_view.size_list->setEnabled(false);
931 m_view.format_list->setEnabled(true);
932 m_view.size_list->setEnabled(true);
935 for (int i = 0; i < m_view.format_list->count() && count < 2; i++) {
936 if (!m_view.format_list->isRowHidden(i)) count++;
938 if (count > 1) m_view.format_list->setVisible(true);
939 else m_view.format_list->setVisible(false);
941 QString group = item->text();
942 bool firstSelected = false;
943 const QStringList formatsList = KdenliveSettings::supportedformats();
944 const QStringList vcodecsList = KdenliveSettings::videocodecs();
945 const QStringList acodecsList = KdenliveSettings::audiocodecs();
947 KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window);
948 const QColor disabled = scheme.foreground(KColorScheme::InactiveText).color();
949 const QColor disabledbg = scheme.background(KColorScheme::NegativeBackground).color();
951 for (int i = 0; i < m_view.size_list->count(); i++) {
952 sizeItem = m_view.size_list->item(i);
953 if ((sizeItem->data(GroupRole).toString() == group || sizeItem->data(GroupRole).toString().isEmpty()) && sizeItem->data(MetaGroupRole).toString() == destination) {
954 std = sizeItem->data(StandardRole).toString();
955 if (!std.isEmpty()) {
956 if (std.contains("PAL", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 0);
957 else if (std.contains("NTSC", Qt::CaseInsensitive)) sizeItem->setHidden(m_view.format_selection->currentIndex() != 1);
959 sizeItem->setHidden(false);
960 if (!firstSelected) m_view.size_list->setCurrentItem(sizeItem);
961 firstSelected = true;
964 if (!sizeItem->isHidden()) {
965 // Make sure the selected profile uses an installed avformat codec / format
966 std = sizeItem->data(ParamsRole).toString();
967 if (!formatsList.isEmpty()) {
969 if (std.startsWith("f=")) format = std.section("f=", 1, 1);
970 else if (std.contains(" f=")) format = std.section(" f=", 1, 1);
971 if (!format.isEmpty()) {
972 format = format.section(' ', 0, 0).toLower();
973 if (!formatsList.contains(format)) {
974 kDebug() << "***** UNSUPPORTED F: " << format;
975 //sizeItem->setHidden(true);
976 //sizeItem->setFlags(Qt::ItemIsSelectable);
977 sizeItem->setToolTip(i18n("Unsupported video format: %1", format));
978 sizeItem->setIcon(brokenIcon);
979 sizeItem->setForeground(disabled);
983 if (!acodecsList.isEmpty() && !sizeItem->isHidden()) {
985 if (std.startsWith("acodec=")) format = std.section("acodec=", 1, 1);
986 else if (std.contains(" acodec=")) format = std.section(" acodec=", 1, 1);
987 if (!format.isEmpty()) {
988 format = format.section(' ', 0, 0).toLower();
989 if (!acodecsList.contains(format)) {
990 kDebug() << "***** UNSUPPORTED ACODEC: " << format;
991 //sizeItem->setHidden(true);
992 //sizeItem->setFlags(Qt::ItemIsSelectable);
993 sizeItem->setToolTip(i18n("Unsupported audio codec: %1", format));
994 sizeItem->setIcon(brokenIcon);
995 sizeItem->setForeground(disabled);
996 sizeItem->setBackground(disabledbg);
1000 if (!vcodecsList.isEmpty() && !sizeItem->isHidden()) {
1002 if (std.startsWith("vcodec=")) format = std.section("vcodec=", 1, 1);
1003 else if (std.contains(" vcodec=")) format = std.section(" vcodec=", 1, 1);
1004 if (!format.isEmpty()) {
1005 format = format.section(' ', 0, 0).toLower();
1006 if (!vcodecsList.contains(format)) {
1007 kDebug() << "***** UNSUPPORTED VCODEC: " << format;
1008 //sizeItem->setHidden(true);
1009 //sizeItem->setFlags(Qt::ItemIsSelectable);
1010 sizeItem->setToolTip(i18n("Unsupported video codec: %1", format));
1011 sizeItem->setIcon(brokenIcon);
1012 sizeItem->setForeground(disabled);
1017 } else sizeItem->setHidden(true);
1019 focusFirstVisibleItem();
1020 m_view.size_list->blockSignals(false);
1024 KUrl RenderWidget::filenameWithExtension(KUrl url, QString extension)
1026 if (url.isEmpty()) url = KUrl(m_projectFolder);
1027 QString directory = url.directory(KUrl::AppendTrailingSlash | KUrl::ObeyTrailingSlash);
1028 QString filename = url.fileName(KUrl::ObeyTrailingSlash);
1031 if (extension.at(0) == '.') ext = extension;
1032 else ext = '.' + extension;
1034 if (filename.isEmpty()) filename = i18n("untitled");
1036 int pos = filename.lastIndexOf('.');
1037 if (pos == 0) filename.append(ext);
1039 if (!filename.endsWith(ext, Qt::CaseInsensitive)) {
1040 filename = filename.left(pos) + ext;
1044 return KUrl(directory + filename);
1049 * Called when a new format or size has been selected.
1051 void RenderWidget::refreshParams()
1053 // Format not available (e.g. codec not installed); Disable start button
1054 QListWidgetItem *item = m_view.size_list->currentItem();
1055 if (!item || item->isHidden()) {
1056 m_view.advanced_params->clear();
1057 m_view.buttonStart->setEnabled(false);
1060 QString params = item->data(ParamsRole).toString();
1061 QString extension = item->data(ExtensionRole).toString();
1062 m_view.advanced_params->setPlainText(params);
1063 QString destination = m_view.destination_list->itemData(m_view.destination_list->currentIndex()).toString();
1064 if (params.contains(" s=") || destination == "audioonly") {
1065 // profile has a fixed size, do not allow resize
1066 m_view.rescale->setEnabled(false);
1067 m_view.rescale_size->setEnabled(false);
1069 m_view.rescale->setEnabled(true);
1070 m_view.rescale_size->setEnabled(true);
1072 KUrl url = filenameWithExtension(m_view.out_file->url(), extension);
1073 m_view.out_file->setUrl(url);
1074 // if (!url.isEmpty()) {
1075 // QString path = url.path();
1076 // int pos = path.lastIndexOf('.') + 1;
1077 // if (pos == 0) path.append('.' + extension);
1078 // else path = path.left(pos) + extension;
1079 // m_view.out_file->setUrl(KUrl(path));
1081 // m_view.out_file->setUrl(KUrl(QDir::homePath() + "/untitled." + extension));
1083 m_view.out_file->setFilter("*." + extension);
1084 QString edit = item->data(EditableRole).toString();
1085 if (edit.isEmpty() || !edit.endsWith("customprofiles.xml")) {
1086 m_view.buttonDelete->setEnabled(false);
1087 m_view.buttonEdit->setEnabled(false);
1089 m_view.buttonDelete->setEnabled(true);
1090 m_view.buttonEdit->setEnabled(true);
1093 m_view.buttonStart->setEnabled(m_view.size_list->currentItem()->toolTip().isEmpty());
1096 void RenderWidget::reloadProfiles()
1101 void RenderWidget::parseProfiles(QString meta, QString group, QString profile)
1103 m_view.size_list->clear();
1104 m_view.format_list->clear();
1105 m_view.destination_list->clear();
1106 m_view.destination_list->addItem(KIcon("video-x-generic"), i18n("File rendering"));
1107 m_view.destination_list->addItem(KIcon("media-optical"), i18n("DVD"), "dvd");
1108 m_view.destination_list->addItem(KIcon("audio-x-generic"), i18n("Audio only"), "audioonly");
1109 m_view.destination_list->addItem(KIcon("applications-internet"), i18n("Web sites"), "websites");
1110 m_view.destination_list->addItem(KIcon("applications-multimedia"), i18n("Media players"), "mediaplayers");
1111 m_view.destination_list->addItem(KIcon("drive-harddisk"), i18n("Lossless / HQ"), "lossless");
1112 m_view.destination_list->addItem(KIcon("pda"), i18n("Mobile devices"), "mobile");
1114 QString exportFile = KStandardDirs::locate("appdata", "export/profiles.xml");
1115 parseFile(exportFile, false);
1118 QString exportFolder = KStandardDirs::locateLocal("appdata", "export/");
1119 QDir directory = QDir(exportFolder);
1122 QStringList fileList = directory.entryList(filter, QDir::Files);
1123 // We should parse customprofiles.xml in last position, so that user profiles
1124 // can also override profiles installed by KNewStuff
1125 fileList.removeAll("customprofiles.xml");
1126 foreach(const QString &filename, fileList)
1127 parseFile(exportFolder + filename, true);
1128 if (QFile::exists(exportFolder + "customprofiles.xml")) parseFile(exportFolder + "customprofiles.xml", true);
1130 if (!meta.isEmpty()) {
1131 m_view.destination_list->blockSignals(true);
1132 m_view.destination_list->setCurrentIndex(m_view.destination_list->findData(meta));
1133 m_view.destination_list->blockSignals(false);
1136 QList<QListWidgetItem *> child;
1137 if (!group.isEmpty()) child = m_view.format_list->findItems(group, Qt::MatchExactly);
1138 if (!child.isEmpty()) {
1139 for (int i = 0; i < child.count(); i++) {
1140 if (child.at(i)->data(MetaGroupRole).toString() == meta) {
1141 m_view.format_list->setCurrentItem(child.at(i));
1147 if (!profile.isEmpty()) child = m_view.size_list->findItems(profile, Qt::MatchExactly);
1148 if (!child.isEmpty()) m_view.size_list->setCurrentItem(child.at(0));
1151 void RenderWidget::parseFile(QString exportFile, bool editable)
1153 kDebug() << "// Parsing file: " << exportFile;
1154 kDebug() << "------------------------------";
1156 QFile file(exportFile);
1157 doc.setContent(&file, false);
1159 QDomElement documentElement;
1160 QDomElement profileElement;
1162 QListWidgetItem *item;
1163 QDomNodeList groups = doc.elementsByTagName("group");
1165 const QStringList acodecsList = KdenliveSettings::audiocodecs();
1166 bool replaceVorbisCodec = false;
1167 if (!acodecsList.contains("vorbis") && acodecsList.contains("libvorbis")) replaceVorbisCodec = true;
1168 bool replaceLibfaacCodec = false;
1169 if (!acodecsList.contains("libfaac") && acodecsList.contains("aac")) replaceLibfaacCodec = true;
1172 if (editable || groups.count() == 0) {
1173 QDomElement profiles = doc.documentElement();
1174 if (editable && profiles.attribute("version", 0).toInt() < 1) {
1175 kDebug() << "// OLD profile version";
1176 // this is an old profile version, update it
1177 QDomDocument newdoc;
1178 QDomElement newprofiles = newdoc.createElement("profiles");
1179 newprofiles.setAttribute("version", 1);
1180 newdoc.appendChild(newprofiles);
1181 QDomNodeList profilelist = doc.elementsByTagName("profile");
1182 for (int i = 0; i < profilelist.count(); i++) {
1183 QString category = i18n("Custom");
1185 QDomNode parent = profilelist.at(i).parentNode();
1186 if (!parent.isNull()) {
1187 QDomElement parentNode = parent.toElement();
1188 if (parentNode.hasAttribute("name")) category = parentNode.attribute("name");
1189 extension = parentNode.attribute("extension");
1191 profilelist.at(i).toElement().setAttribute("category", category);
1192 if (!extension.isEmpty()) profilelist.at(i).toElement().setAttribute("extension", extension);
1193 QDomNode n = profilelist.at(i).cloneNode();
1194 newprofiles.appendChild(newdoc.importNode(n, true));
1196 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
1197 KMessageBox::sorry(this, i18n("Unable to write to file %1", exportFile));
1200 QTextStream out(&file);
1201 out << newdoc.toString();
1203 parseFile(exportFile, editable);
1207 QDomNode node = doc.elementsByTagName("profile").at(0);
1208 if (node.isNull()) {
1209 kDebug() << "// Export file: " << exportFile << " IS BROKEN";
1213 while (!node.isNull()) {
1214 QDomElement profile = node.toElement();
1215 QString profileName = profile.attribute("name");
1216 QString standard = profile.attribute("standard");
1217 QString params = profile.attribute("args");
1219 if (replaceVorbisCodec && params.contains("acodec=vorbis")) {
1220 // replace vorbis with libvorbis
1221 params = params.replace("vorbis", "libvorbis");
1223 if (replaceLibfaacCodec && params.contains("acodec=libfaac")) {
1224 // replace libfaac with aac
1225 params = params.replace("libfaac", "aac");
1228 QString category = profile.attribute("category", i18n("Custom"));
1229 QString dest = profile.attribute("destinationid");
1230 QString prof_extension = profile.attribute("extension");
1231 if (!prof_extension.isEmpty()) extension = prof_extension;
1233 QList <QListWidgetItem *> list = m_view.format_list->findItems(category, Qt::MatchExactly);
1234 bool exists = false;
1235 for (int j = 0; j < list.count(); j++) {
1236 if (list.at(j)->data(MetaGroupRole) == dest) {
1242 item = new QListWidgetItem(category, m_view.format_list);
1243 item->setData(MetaGroupRole, dest);
1246 // Check if item with same name already exists and replace it,
1247 // allowing to override default profiles
1249 list = m_view.size_list->findItems(profileName, Qt::MatchExactly);
1251 for (int j = 0; j < list.count(); j++) {
1252 if (list.at(j)->data(MetaGroupRole) == dest) {
1253 QListWidgetItem *duplicate = list.takeAt(j);
1259 item = new QListWidgetItem(profileName, m_view.size_list);
1260 //kDebug() << "// ADDINg item with name: " << profileName << ", GRP" << category << ", DEST:" << dest ;
1261 item->setData(GroupRole, category);
1262 item->setData(MetaGroupRole, dest);
1263 item->setData(ExtensionRole, extension);
1264 item->setData(RenderRole, "avformat");
1265 item->setData(StandardRole, standard);
1266 item->setData(ParamsRole, params);
1267 if (profile.hasAttribute("url")) item->setData(ExtraRole, profile.attribute("url"));
1269 item->setData(EditableRole, exportFile);
1270 if (exportFile.endsWith("customprofiles.xml")) item->setIcon(KIcon("emblem-favorite"));
1271 else item->setIcon(KIcon("applications-internet"));
1273 node = doc.elementsByTagName("profile").at(count);
1281 QString profileName;
1283 QString prof_extension;
1289 while (!groups.item(i).isNull()) {
1290 documentElement = groups.item(i).toElement();
1291 QDomNode gname = documentElement.elementsByTagName("groupname").at(0);
1292 QString metagroupName;
1293 QString metagroupId;
1294 if (!gname.isNull()) {
1295 metagroupName = gname.firstChild().nodeValue();
1296 metagroupId = gname.toElement().attribute("id");
1298 if (!metagroupName.isEmpty() && m_view.destination_list->findData(metagroupId) == -1) {
1299 if (metagroupId == "dvd") icon = KIcon("media-optical");
1300 else if (metagroupId == "audioonly") icon = KIcon("audio-x-generic");
1301 else if (metagroupId == "websites") icon = KIcon("applications-internet");
1302 else if (metagroupId == "mediaplayers") icon = KIcon("applications-multimedia");
1303 else if (metagroupId == "lossless") icon = KIcon("drive-harddisk");
1304 else if (metagroupId == "mobile") icon = KIcon("pda");
1305 m_view.destination_list->addItem(icon, i18n(metagroupName.toUtf8().data()), metagroupId);
1308 groupName = documentElement.attribute("name", i18n("Custom"));
1309 extension = documentElement.attribute("extension", QString());
1310 renderer = documentElement.attribute("renderer", QString());
1311 QList <QListWidgetItem *> list = m_view.format_list->findItems(groupName, Qt::MatchExactly);
1312 bool exists = false;
1313 for (int j = 0; j < list.count(); j++) {
1314 if (list.at(j)->data(MetaGroupRole) == metagroupId) {
1320 item = new QListWidgetItem(groupName, m_view.format_list);
1321 item->setData(MetaGroupRole, metagroupId);
1324 QDomNode n = groups.item(i).firstChild();
1325 while (!n.isNull()) {
1326 if (n.toElement().tagName() != "profile") {
1327 n = n.nextSibling();
1330 profileElement = n.toElement();
1331 profileName = profileElement.attribute("name");
1332 standard = profileElement.attribute("standard");
1333 params = profileElement.attribute("args");
1335 if (replaceVorbisCodec && params.contains("acodec=vorbis")) {
1336 // replace vorbis with libvorbis
1337 params = params.replace("vorbis", "libvorbis");
1339 if (replaceLibfaacCodec && params.contains("acodec=libfaac")) {
1340 // replace libfaac with aac
1341 params = params.replace("libfaac", "aac");
1344 prof_extension = profileElement.attribute("extension");
1345 if (!prof_extension.isEmpty()) extension = prof_extension;
1346 item = new QListWidgetItem(profileName, m_view.size_list);
1347 item->setData(GroupRole, groupName);
1348 item->setData(MetaGroupRole, metagroupId);
1349 item->setData(ExtensionRole, extension);
1350 item->setData(RenderRole, renderer);
1351 item->setData(StandardRole, standard);
1352 item->setData(ParamsRole, params);
1353 if (profileElement.hasAttribute("url")) item->setData(ExtraRole, profileElement.attribute("url"));
1354 if (editable) item->setData(EditableRole, exportFile);
1355 n = n.nextSibling();
1362 void RenderWidget::setRenderJob(const QString &dest, int progress)
1364 QTreeWidgetItem *item;
1365 QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
1366 if (!existing.isEmpty()) item = existing.at(0);
1368 item = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
1369 item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1370 if (progress == 0) {
1371 item->setData(1, Qt::UserRole + 2, WAITINGJOB);
1372 item->setIcon(0, KIcon("media-playback-pause"));
1373 item->setData(1, Qt::UserRole, i18n("Waiting..."));
1376 item->setData(2, Qt::UserRole, progress);
1377 item->setData(1, Qt::UserRole + 2, RUNNINGJOB);
1378 if (progress == 0) {
1379 item->setIcon(0, KIcon("system-run"));
1380 item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1381 item->setData(1, Qt::UserRole + 1, QTime::currentTime());
1384 QTime startTime = item->data(1, Qt::UserRole + 1).toTime();
1385 int seconds = startTime.secsTo(QTime::currentTime());;
1386 const QString t = i18n("Estimated time %1", QTime().addSecs(seconds * (100 - progress) / progress).toString("hh:mm:ss"));
1387 item->setData(1, Qt::UserRole, t);
1391 void RenderWidget::setRenderStatus(const QString &dest, int status, const QString &error)
1393 QTreeWidgetItem *item;
1394 QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(dest, Qt::MatchExactly, 1);
1395 if (!existing.isEmpty()) item = existing.at(0);
1397 item = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << dest << QString());
1398 item->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1400 item->setData(1, Qt::UserRole + 2, FINISHEDJOB);
1402 // Job finished successfully
1403 item->setIcon(0, KIcon("dialog-ok"));
1404 item->setData(2, Qt::UserRole, 100);
1405 QTime startTime = item->data(1, Qt::UserRole + 1).toTime();
1406 int seconds = startTime.secsTo(QTime::currentTime());
1407 const QTime tm = QTime().addSecs(seconds);
1408 const QString t = i18n("Rendering finished in %1", tm.toString("hh:mm:ss"));
1409 item->setData(1, Qt::UserRole, t);
1410 QString itemGroup = item->data(0, Qt::UserRole).toString();
1411 if (itemGroup == "dvd") {
1412 emit openDvdWizard(item->text(1), item->data(0, Qt::UserRole + 1).toString());
1413 } else if (itemGroup == "websites") {
1414 QString url = item->data(0, Qt::UserRole + 1).toString();
1415 if (!url.isEmpty()) new KRun(url, this);
1417 } else if (status == -2) {
1418 // Rendering crashed
1419 item->setData(1, Qt::UserRole, i18n("Rendering crashed"));
1420 item->setIcon(0, KIcon("dialog-close"));
1421 item->setData(2, Qt::UserRole, 100);
1422 m_view.error_log->append(i18n("<strong>Rendering of %1 crashed</strong><br />", dest));
1423 m_view.error_log->append(error);
1424 m_view.error_log->append("<hr />");
1425 m_view.error_box->setVisible(true);
1426 } else if (status == -3) {
1428 item->setData(1, Qt::UserRole, i18n("Rendering aborted"));
1429 item->setIcon(0, KIcon("dialog-cancel"));
1430 item->setData(2, Qt::UserRole, 100);
1433 checkRenderStatus();
1436 void RenderWidget::slotAbortCurrentJob()
1438 QTreeWidgetItem *current = m_view.running_jobs->currentItem();
1440 if (current->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB)
1441 emit abortProcess(current->text(1));
1445 checkRenderStatus();
1450 void RenderWidget::slotCheckJob()
1452 bool activate = false;
1453 QTreeWidgetItem *current = m_view.running_jobs->currentItem();
1455 if (current->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB)
1456 m_view.abort_job->setText(i18n("Abort Job"));
1457 else m_view.abort_job->setText(i18n("Remove Job"));
1460 m_view.abort_job->setEnabled(activate);
1463 void RenderWidget::slotCLeanUpJobs()
1466 QTreeWidgetItem *current = m_view.running_jobs->topLevelItem(ix);
1468 if (current->data(1, Qt::UserRole + 2).toInt() == FINISHEDJOB)
1471 current = m_view.running_jobs->topLevelItem(ix);
1476 void RenderWidget::parseScriptFiles()
1478 QStringList scriptsFilter;
1479 scriptsFilter << "*.sh";
1480 m_view.scripts_list->clear();
1482 QTreeWidgetItem *item;
1483 // List the project scripts
1484 QStringList scriptFiles = QDir(m_projectFolder + "scripts").entryList(scriptsFilter, QDir::Files);
1485 for (int i = 0; i < scriptFiles.size(); ++i) {
1486 KUrl scriptpath(m_projectFolder + "scripts/" + scriptFiles.at(i));
1490 QFile file(scriptpath.path());
1491 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
1492 while (!file.atEnd()) {
1493 QByteArray line = file.readLine();
1494 if (line.startsWith("TARGET=")) {
1495 target = QString(line).section("TARGET=", 1).simplified();
1496 target.remove(QChar('"'));
1497 } else if (line.startsWith("RENDERER=")) {
1498 renderer = QString(line).section("RENDERER=", 1).simplified();
1499 renderer.remove(QChar('"'));
1500 } else if (line.startsWith("MELT=")) {
1501 melt = QString(line).section("MELT=", 1).simplified();
1502 melt.remove(QChar('"'));
1507 if (target.isEmpty()) continue;
1508 item = new QTreeWidgetItem(m_view.scripts_list, QStringList() << QString() << scriptpath.fileName());
1509 if (!renderer.isEmpty() && renderer.contains('/') && !QFile::exists(renderer)) {
1510 item->setIcon(0, KIcon("dialog-cancel"));
1511 item->setToolTip(1, i18n("Script contains wrong command: %1", renderer));
1512 item->setData(0, Qt::UserRole, '1');
1513 } else if (!melt.isEmpty() && melt.contains('/') && !QFile::exists(melt)) {
1514 item->setIcon(0, KIcon("dialog-cancel"));
1515 item->setToolTip(1, i18n("Script contains wrong command: %1", melt));
1516 item->setData(0, Qt::UserRole, '1');
1517 } else item->setIcon(0, KIcon("application-x-executable-script"));
1518 item->setSizeHint(0, QSize(m_view.scripts_list->columnWidth(0), fontMetrics().height() * 2));
1519 item->setData(1, Qt::UserRole, target.simplified());
1520 item->setData(1, Qt::UserRole + 1, scriptpath.path());
1522 bool activate = false;
1523 QTreeWidgetItem *script = m_view.scripts_list->topLevelItem(0);
1525 script->setSelected(true);
1526 m_view.scripts_list->setCurrentItem(script);
1529 // m_view.start_script->setEnabled(activate);
1530 // m_view.delete_script->setEnabled(activate);
1533 void RenderWidget::slotCheckScript()
1535 QTreeWidgetItem *item = m_view.scripts_list->currentItem();
1536 if (item == NULL) return;
1537 m_view.start_script->setEnabled(item->data(0, Qt::UserRole).toString().isEmpty());
1538 m_view.delete_script->setEnabled(true);
1541 void RenderWidget::slotStartScript()
1543 QTreeWidgetItem *item = m_view.scripts_list->currentItem();
1545 QString destination = item->data(1, Qt::UserRole).toString();
1546 QString path = item->data(1, Qt::UserRole + 1).toString();
1547 // Insert new job in queue
1548 QTreeWidgetItem *renderItem;
1549 QList<QTreeWidgetItem *> existing = m_view.running_jobs->findItems(destination, Qt::MatchExactly, 1);
1550 kDebug() << "------ START SCRIPT";
1551 if (!existing.isEmpty()) {
1552 renderItem = existing.at(0);
1553 if (renderItem->data(1, Qt::UserRole + 2).toInt() == RUNNINGJOB) {
1554 KMessageBox::information(this, i18n("There is already a job writing file:<br><b>%1</b><br>Abort the job if you want to overwrite it...", destination), i18n("Already running"));
1557 } else renderItem = new QTreeWidgetItem(m_view.running_jobs, QStringList() << QString() << destination << QString());
1558 kDebug() << "------ START SCRIPT 2";
1559 renderItem->setData(2, Qt::UserRole, 0);
1560 renderItem->setData(1, Qt::UserRole + 2, WAITINGJOB);
1561 renderItem->setIcon(0, KIcon("media-playback-pause"));
1562 renderItem->setData(1, Qt::UserRole, i18n("Waiting..."));
1563 renderItem->setSizeHint(1, QSize(m_view.running_jobs->columnWidth(1), fontMetrics().height() * 2));
1564 renderItem->setData(1, Qt::UserRole + 1, QTime::currentTime());
1565 renderItem->setData(1, Qt::UserRole + 3, path);
1566 renderItem->setData(1, Qt::UserRole + 4, '1');
1567 checkRenderStatus();
1568 m_view.tabWidget->setCurrentIndex(1);
1572 void RenderWidget::slotDeleteScript()
1574 QTreeWidgetItem *item = m_view.scripts_list->currentItem();
1576 QString path = item->data(1, Qt::UserRole + 1).toString();
1577 KIO::NetAccess::del(path + ".mlt", this);
1578 KIO::NetAccess::del(path, this);
1583 void RenderWidget::slotGenerateScript()
1585 slotPrepareExport(true);
1588 void RenderWidget::slotHideLog()
1590 m_view.error_box->setVisible(false);
1593 void RenderWidget::setRenderProfile(const QString &dest, const QString &name, const QString &url)
1595 m_view.destination_list->blockSignals(true);
1596 m_view.format_list->blockSignals(true);
1597 m_view.size_list->blockSignals(true);
1599 if (!url.isEmpty()) m_view.out_file->setUrl(KUrl(url));
1601 for (int i = 0; i < m_view.destination_list->count(); i++) {
1602 if (m_view.destination_list->itemData(i, Qt::UserRole) == dest) {
1603 m_view.destination_list->setCurrentIndex(i);
1607 QList<QListWidgetItem *> childs = m_view.size_list->findItems(name, Qt::MatchExactly);
1608 if (!childs.isEmpty()) {
1609 QListWidgetItem *profile = childs.at(0);
1610 if (profile->isHidden()) {
1611 QString group = profile->data(GroupRole).toString();
1612 childs = m_view.format_list->findItems(group, Qt::MatchExactly);
1613 if (!childs.isEmpty()) {
1614 m_view.format_list->setCurrentItem(childs.at(0));
1618 m_view.size_list->blockSignals(false);
1619 m_view.size_list->setCurrentItem(profile);
1620 } else m_view.size_list->blockSignals(false);
1621 m_view.destination_list->blockSignals(false);
1622 m_view.format_list->blockSignals(false);
1626 bool RenderWidget::startWaitingRenderJobs()
1628 m_blockProcessing = true;
1629 QString autoscriptFile = getFreeScriptName("auto");
1630 QFile file(autoscriptFile);
1631 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
1632 kWarning() << "////// ERROR writing to file: " << autoscriptFile;
1633 KMessageBox::error(0, i18n("Cannot write to file %1", autoscriptFile));
1637 QTextStream outStream(&file);
1638 outStream << "#! /bin/sh" << "\n" << "\n";
1639 QTreeWidgetItem *item = m_view.running_jobs->topLevelItem(0);
1641 if (item->data(1, Qt::UserRole + 2).toInt() == WAITINGJOB) {
1642 if (item->data(1, Qt::UserRole + 4).isNull()) {
1643 // Add render process for item
1644 const QString params = item->data(1, Qt::UserRole + 3).toStringList().join(" ");
1645 outStream << m_renderer << " " << params << "\n";
1648 outStream << item->data(1, Qt::UserRole + 3).toString() << "\n";
1651 item = m_view.running_jobs->itemBelow(item);
1653 // erase itself when rendering is finished
1654 outStream << "rm " << autoscriptFile << "\n" << "\n";
1655 if (file.error() != QFile::NoError) {
1656 KMessageBox::error(0, i18n("Cannot write to file %1", autoscriptFile));
1658 m_blockProcessing = false;
1662 QFile::setPermissions(autoscriptFile, file.permissions() | QFile::ExeUser);
1663 QProcess::startDetached(autoscriptFile, QStringList());
1667 QString RenderWidget::getFreeScriptName(const QString &prefix)
1670 QString scriptsFolder = m_projectFolder + "scripts/";
1671 KStandardDirs::makeDir(scriptsFolder);
1673 while (path.isEmpty() || QFile::exists(path)) {
1675 path = scriptsFolder + prefix + i18n("script") + QString::number(ix).rightJustified(3, '0', false) + ".sh";
1680 void RenderWidget::slotPlayRendering(QTreeWidgetItem *item, int)
1682 if (KdenliveSettings::defaultplayerapp().isEmpty() || item->data(1, Qt::UserRole + 2).toInt() != FINISHEDJOB) return;
1683 const QByteArray startId = KStartupInfo::createNewStartupId();
1684 const QString command = KdenliveSettings::defaultplayerapp() + ' ' + item->text(1);
1685 KRun::runCommand(command, KdenliveSettings::defaultplayerapp(), KdenliveSettings::defaultplayerapp(), this, startId);