renderwidget.cpp 132 KB
Newer Older
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/***************************************************************************
 *   Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
 ***************************************************************************/

20
#include "renderwidget.h"
21
#include "bin/projectitemmodel.h"
22
#include "bin/bin.h"
Nicolas Carion's avatar
linting    
Nicolas Carion committed
23
#include "core.h"
Nicolas Carion's avatar
Nicolas Carion committed
24
#include "dialogs/profilesdialog.h"
25
#include "doc/kdenlivedoc.h"
26
#include "kdenlivesettings.h"
27
#include "monitor/monitor.h"
Nicolas Carion's avatar
Nicolas Carion committed
28
29
#include "profiles/profilemodel.hpp"
#include "profiles/profilerepository.hpp"
30
#include "project/projectmanager.h"
31
#include "timecode.h"
Nicolas Carion's avatar
Nicolas Carion committed
32
#include "ui_saveprofile_ui.h"
33
#include "xml/xml.hpp"
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
34

35
#include "klocalizedstring.h"
36
#include <KColorScheme>
37
#include <KIO/DesktopExecParser>
Nicolas Carion's avatar
Nicolas Carion committed
38
39
40
#include <KMessageBox>
#include <KNotification>
#include <KRun>
41
#include <kio_version.h>
Nicolas Carion's avatar
Nicolas Carion committed
42
#include <knotifications_version.h>
43
#include <kns3/downloaddialog.h>
44

Laurent Montel's avatar
Laurent Montel committed
45
#include "kdenlive_debug.h"
Nicolas Carion's avatar
Nicolas Carion committed
46
47
#include <QDBusConnectionInterface>
#include <QDir>
48
#include <QDomDocument>
49
#include <QFileIconProvider>
50
51
#include <QHeaderView>
#include <QInputDialog>
52
53
#include <QJsonArray>
#include <QJsonObject>
Nicolas Carion's avatar
Nicolas Carion committed
54
55
#include <QKeyEvent>
#include <QMimeDatabase>
56
#include <QProcess>
57
#include <QStandardPaths>
58
#include <QTemporaryFile>
Nicolas Carion's avatar
Nicolas Carion committed
59
60
61
62
#include <QThread>
#include <QTreeWidgetItem>
#include <qglobal.h>
#include <qstring.h>
63
#include <QMenu>
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
64

65
66
67
68
69
#ifdef KF5_USE_PURPOSE
#include <Purpose/AlternativesModel>
#include <PurposeWidgets/Menu>
#endif

Vincent Pinon's avatar
Vincent Pinon committed
70
#include <locale>
71
72
73
#ifdef Q_OS_MAC
#include <xlocale.h>
#endif
74

75
// Render profiles roles
Nicolas Carion's avatar
Nicolas Carion committed
76
77
78
79
80
81
82
83
84
85
86
87
88
enum {
    GroupRole = Qt::UserRole,
    ExtensionRole,
    StandardRole,
    RenderRole,
    ParamsRole,
    EditableRole,
    ExtraRole,
    BitratesRole,
    DefaultBitrateRole,
    AudioBitratesRole,
    DefaultAudioBitrateRole,
    SpeedsRole,
89
    FieldRole,
Nicolas Carion's avatar
Nicolas Carion committed
90
91
    ErrorRole
};
92

93
94
95
// Render job roles
const int ParametersRole = Qt::UserRole + 1;
const int TimeRole = Qt::UserRole + 2;
96
97
const int ProgressRole = Qt::UserRole + 3;
const int ExtraInfoRole = Qt::UserRole + 5;
98

99
// Running job status
Nicolas Carion's avatar
Nicolas Carion committed
100
enum JOBSTATUS { WAITINGJOB = 0, STARTINGJOB, RUNNINGJOB, FINISHEDJOB, FAILEDJOB, ABORTEDJOB };
101

102
103
104
static QStringList acodecsList;
static QStringList vcodecsList;
static QStringList supportedFormats;
105

106
107
108
RenderJobItem::RenderJobItem(QTreeWidget *parent, const QStringList &strings, int type)
    : QTreeWidgetItem(parent, strings, type)
    , m_status(-1)
109
{
110
    setSizeHint(1, QSize(parent->columnWidth(1), parent->fontMetrics().height() * 3));
111
112
113
114
115
    setStatus(WAITINGJOB);
}

void RenderJobItem::setStatus(int status)
{
116
    if (m_status == status) {
Laurent Montel's avatar
Laurent Montel committed
117
        return;
118
    }
119
120
    m_status = status;
    switch (status) {
121
    case WAITINGJOB:
122
        setIcon(0, QIcon::fromTheme(QStringLiteral("media-playback-pause")));
123
124
125
126
        setData(1, Qt::UserRole, i18n("Waiting..."));
        break;
    case FINISHEDJOB:
        setData(1, Qt::UserRole, i18n("Rendering finished"));
127
        setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-ok")));
128
129
130
131
        setData(1, ProgressRole, 100);
        break;
    case FAILEDJOB:
        setData(1, Qt::UserRole, i18n("Rendering crashed"));
132
        setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-close")));
133
134
135
136
        setData(1, ProgressRole, 100);
        break;
    case ABORTEDJOB:
        setData(1, Qt::UserRole, i18n("Rendering aborted"));
137
        setIcon(0, QIcon::fromTheme(QStringLiteral("dialog-cancel")));
138
        setData(1, ProgressRole, 100);
Vincent Pinon's avatar
Vincent Pinon committed
139
        break;
140
141
    default:
        break;
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
    }
}

int RenderJobItem::status() const
{
    return m_status;
}

void RenderJobItem::setMetadata(const QString &data)
{
    m_data = data;
}

const QString RenderJobItem::metadata() const
{
    return m_data;
}
159

160
RenderWidget::RenderWidget(bool enableProxy, QWidget *parent)
161
162
    : QDialog(parent)
    , m_blockProcessing(false)
163
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
164
    m_view.setupUi(this);
165
166
    int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
    QSize iconSize(size, size);
167

168
    setWindowTitle(i18n("Rendering"));
169
170
171
172
    m_view.buttonDelete->setIconSize(iconSize);
    m_view.buttonEdit->setIconSize(iconSize);
    m_view.buttonSave->setIconSize(iconSize);
    m_view.buttonFavorite->setIconSize(iconSize);
173
    m_view.buttonDownload->setIconSize(iconSize);
174

175
    m_view.buttonDelete->setIcon(QIcon::fromTheme(QStringLiteral("trash-empty")));
176
177
178
    m_view.buttonDelete->setToolTip(i18n("Delete profile"));
    m_view.buttonDelete->setEnabled(false);

179
    m_view.buttonEdit->setIcon(QIcon::fromTheme(QStringLiteral("document-edit")));
180
181
182
    m_view.buttonEdit->setToolTip(i18n("Edit profile"));
    m_view.buttonEdit->setEnabled(false);

183
    m_view.buttonSave->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
184
185
    m_view.buttonSave->setToolTip(i18n("Create new profile"));

186
    m_view.hide_log->setIcon(QIcon::fromTheme(QStringLiteral("go-down")));
187

188
    m_view.buttonFavorite->setIcon(QIcon::fromTheme(QStringLiteral("favorite")));
189
    m_view.buttonFavorite->setToolTip(i18n("Copy profile to favorites"));
190

191
192
193
    m_view.buttonDownload->setIcon(QIcon::fromTheme(QStringLiteral("edit-download")));
    m_view.buttonDownload->setToolTip(i18n("Download New Render Profiles..."));

194
    m_view.out_file->button()->setToolTip(i18n("Select output destination"));
195
    m_view.advanced_params->setMaximumHeight(QFontMetrics(font()).lineSpacing() * 5);
196

197
    m_view.optionsGroup->setVisible(m_view.options->isChecked());
Laurent Montel's avatar
Laurent Montel committed
198
    connect(m_view.options, &QAbstractButton::toggled, m_view.optionsGroup, &QWidget::setVisible);
199
    m_view.videoLabel->setVisible(m_view.options->isChecked());
Laurent Montel's avatar
Laurent Montel committed
200
    connect(m_view.options, &QAbstractButton::toggled, m_view.videoLabel, &QWidget::setVisible);
201
    m_view.video->setVisible(m_view.options->isChecked());
Laurent Montel's avatar
Laurent Montel committed
202
    connect(m_view.options, &QAbstractButton::toggled, m_view.video, &QWidget::setVisible);
203
    m_view.audioLabel->setVisible(m_view.options->isChecked());
Laurent Montel's avatar
Laurent Montel committed
204
    connect(m_view.options, &QAbstractButton::toggled, m_view.audioLabel, &QWidget::setVisible);
205
    m_view.audio->setVisible(m_view.options->isChecked());
Laurent Montel's avatar
Laurent Montel committed
206
207
    connect(m_view.options, &QAbstractButton::toggled, m_view.audio, &QWidget::setVisible);
    connect(m_view.quality, &QAbstractSlider::valueChanged, this, &RenderWidget::adjustAVQualities);
208
    connect(m_view.video, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &RenderWidget::adjustQuality);
Laurent Montel's avatar
Laurent Montel committed
209
    connect(m_view.speed, &QAbstractSlider::valueChanged, this, &RenderWidget::adjustSpeed);
210

211
212
    m_view.buttonRender->setEnabled(false);
    m_view.buttonGenerateScript->setEnabled(false);
213
    setRescaleEnabled(false);
214
215
216
217
218
219
220
    m_view.guides_box->setVisible(false);
    m_view.open_dvd->setVisible(false);
    m_view.create_chapter->setVisible(false);
    m_view.open_browser->setVisible(false);
    m_view.error_box->setVisible(false);
    m_view.tc_type->setEnabled(false);
    m_view.checkTwoPass->setEnabled(false);
221
    m_view.proxy_render->setHidden(!enableProxy);
222
    connect(m_view.proxy_render, &QCheckBox::toggled, this, &RenderWidget::slotProxyWarn);
223
    KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window);
224
    QColor bg = scheme.background(KColorScheme::NegativeBackground).color();
Nicolas Carion's avatar
Nicolas Carion committed
225
226
    m_view.errorBox->setStyleSheet(
        QStringLiteral("QGroupBox { background-color: rgb(%1, %2, %3); border-radius: 5px;}; ").arg(bg.red()).arg(bg.green()).arg(bg.blue()));
227
    int height = QFontInfo(font()).pixelSize();
228
    m_view.errorIcon->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(height, height));
229
230
231
    m_view.errorBox->setHidden(true);

    m_infoMessage = new KMessageWidget;
232
    m_view.info->addWidget(m_infoMessage);
233
234
235
    m_infoMessage->setCloseButtonVisible(false);
    m_infoMessage->hide();

236
237
238
239
240
    m_jobInfoMessage = new KMessageWidget;
    m_view.jobInfo->addWidget(m_jobInfoMessage);
    m_jobInfoMessage->setCloseButtonVisible(false);
    m_jobInfoMessage->hide();

241
    m_view.encoder_threads->setMinimum(0);
242
    m_view.encoder_threads->setMaximum(QThread::idealThreadCount());
243
    m_view.encoder_threads->setToolTip(i18n("Encoding threads (0 is automatic)"));
244
    m_view.encoder_threads->setValue(KdenliveSettings::encodethreads());
245
    connect(m_view.encoder_threads, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &RenderWidget::slotUpdateEncodeThreads);
246

247
    m_view.rescale_keep->setChecked(KdenliveSettings::rescalekeepratio());
248
249
    connect(m_view.rescale_width, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &RenderWidget::slotUpdateRescaleWidth);
    connect(m_view.rescale_height, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &RenderWidget::slotUpdateRescaleHeight);
250
    m_view.rescale_keep->setIcon(QIcon::fromTheme(QStringLiteral("edit-link")));
251
    m_view.rescale_keep->setToolTip(i18n("Preserve aspect ratio"));
Laurent Montel's avatar
Laurent Montel committed
252
    connect(m_view.rescale_keep, &QAbstractButton::clicked, this, &RenderWidget::slotSwitchAspectRatio);
253

254
    connect(m_view.buttonRender, SIGNAL(clicked()), this, SLOT(slotPrepareExport()));
Laurent Montel's avatar
Laurent Montel committed
255
    connect(m_view.buttonGenerateScript, &QAbstractButton::clicked, this, &RenderWidget::slotGenerateScript);
256

257
258
259
260
    m_view.abort_job->setEnabled(false);
    m_view.start_script->setEnabled(false);
    m_view.delete_script->setEnabled(false);

Laurent Montel's avatar
Laurent Montel committed
261
    connect(m_view.export_audio, &QCheckBox::stateChanged, this, &RenderWidget::slotUpdateAudioLabel);
262
263
    m_view.export_audio->setCheckState(Qt::PartiallyChecked);

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
264
    checkCodecs();
265
    parseProfiles();
266
    parseScriptFiles();
267
    m_view.running_jobs->setUniformRowHeights(false);
268
269
    m_view.running_jobs->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(m_view.running_jobs, &QTreeWidget::customContextMenuRequested, this, &RenderWidget::prepareMenu);
270
    m_view.scripts_list->setUniformRowHeights(false);
Laurent Montel's avatar
Laurent Montel committed
271
272
273
274
275
276
277
278
279
280
    connect(m_view.start_script, &QAbstractButton::clicked, this, &RenderWidget::slotStartScript);
    connect(m_view.delete_script, &QAbstractButton::clicked, this, &RenderWidget::slotDeleteScript);
    connect(m_view.scripts_list, &QTreeWidget::itemSelectionChanged, this, &RenderWidget::slotCheckScript);
    connect(m_view.running_jobs, &QTreeWidget::itemSelectionChanged, this, &RenderWidget::slotCheckJob);
    connect(m_view.running_jobs, &QTreeWidget::itemDoubleClicked, this, &RenderWidget::slotPlayRendering);

    connect(m_view.buttonSave, &QAbstractButton::clicked, this, &RenderWidget::slotSaveProfile);
    connect(m_view.buttonEdit, &QAbstractButton::clicked, this, &RenderWidget::slotEditProfile);
    connect(m_view.buttonDelete, &QAbstractButton::clicked, this, &RenderWidget::slotDeleteProfile);
    connect(m_view.buttonFavorite, &QAbstractButton::clicked, this, &RenderWidget::slotCopyToFavorites);
281
    connect(m_view.buttonDownload, &QAbstractButton::clicked, this, &RenderWidget::slotDownloadNewRenderProfiles);
Laurent Montel's avatar
Laurent Montel committed
282
283
284
285
286
287
288
289
290
291

    connect(m_view.abort_job, &QAbstractButton::clicked, this, &RenderWidget::slotAbortCurrentJob);
    connect(m_view.start_job, &QAbstractButton::clicked, this, &RenderWidget::slotStartCurrentJob);
    connect(m_view.clean_up, &QAbstractButton::clicked, this, &RenderWidget::slotCLeanUpJobs);
    connect(m_view.hide_log, &QAbstractButton::clicked, this, &RenderWidget::slotHideLog);

    connect(m_view.buttonClose, &QAbstractButton::clicked, this, &QWidget::hide);
    connect(m_view.buttonClose2, &QAbstractButton::clicked, this, &QWidget::hide);
    connect(m_view.buttonClose3, &QAbstractButton::clicked, this, &QWidget::hide);
    connect(m_view.rescale, &QAbstractButton::toggled, this, &RenderWidget::setRescaleEnabled);
292
293
    connect(m_view.out_file, &KUrlRequester::textChanged, this, static_cast<void (RenderWidget::*)()>(&RenderWidget::slotUpdateButtons));
    connect(m_view.out_file, &KUrlRequester::urlSelected, this, static_cast<void (RenderWidget::*)(const QUrl &)>(&RenderWidget::slotUpdateButtons));
294

Laurent Montel's avatar
Laurent Montel committed
295
296
    connect(m_view.formats, &QTreeWidget::currentItemChanged, this, &RenderWidget::refreshParams);
    connect(m_view.formats, &QTreeWidget::itemDoubleClicked, this, &RenderWidget::slotEditItem);
297

Laurent Montel's avatar
Laurent Montel committed
298
299
300
    connect(m_view.render_guide, &QAbstractButton::clicked, this, &RenderWidget::slotUpdateGuideBox);
    connect(m_view.render_zone, &QAbstractButton::clicked, this, &RenderWidget::slotUpdateGuideBox);
    connect(m_view.render_full, &QAbstractButton::clicked, this, &RenderWidget::slotUpdateGuideBox);
301

302
303
    connect(m_view.guide_end, static_cast<void (KComboBox::*)(int)>(&KComboBox::activated), this, &RenderWidget::slotCheckStartGuidePosition);
    connect(m_view.guide_start, static_cast<void (KComboBox::*)(int)>(&KComboBox::activated), this, &RenderWidget::slotCheckEndGuidePosition);
304

Laurent Montel's avatar
Laurent Montel committed
305
    connect(m_view.tc_overlay, &QAbstractButton::toggled, m_view.tc_type, &QWidget::setEnabled);
306

Nicolas Carion's avatar
Nicolas Carion committed
307
308
    // m_view.splitter->setStretchFactor(1, 5);
    // m_view.splitter->setStretchFactor(0, 2);
309

310
    m_view.out_file->setAcceptMode(QFileDialog::AcceptSave);
311
    m_view.out_file->setMode(KFile::File);
312
    m_view.out_file->setFocusPolicy(Qt::ClickFocus);
313

314
    m_jobsDelegate = new RenderViewDelegate(this);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
315
    m_view.running_jobs->setHeaderLabels(QStringList() << QString() << i18n("File"));
316
    m_view.running_jobs->setItemDelegate(m_jobsDelegate);
317

318
    QHeaderView *header = m_view.running_jobs->header();
319
    header->setSectionResizeMode(0, QHeaderView::Fixed);
320
    header->resizeSection(0, size + 4);
321
    header->setSectionResizeMode(1, QHeaderView::Interactive);
322

323
    m_view.scripts_list->setHeaderLabels(QStringList() << QString() << i18n("Stored Playlists"));
324
325
    m_scriptsDelegate = new RenderViewDelegate(this);
    m_view.scripts_list->setItemDelegate(m_scriptsDelegate);
326
    header = m_view.scripts_list->header();
327
    header->setSectionResizeMode(0, QHeaderView::Fixed);
328
    header->resizeSection(0, size + 4);
329

Nicolas Carion's avatar
Nicolas Carion committed
330
// Find path for Kdenlive renderer
331
332
333
#ifdef Q_OS_WIN
    m_renderer = QCoreApplication::applicationDirPath() + QStringLiteral("/kdenlive_render.exe");
#else
334
    m_renderer = QCoreApplication::applicationDirPath() + QStringLiteral("/kdenlive_render");
335
#endif
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
336
    if (!QFile::exists(m_renderer)) {
337
        m_renderer = QStandardPaths::findExecutable(QStringLiteral("kdenlive_render"));
338
        if (m_renderer.isEmpty()) {
339
            m_renderer = QStringLiteral("kdenlive_render");
340
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
341
342
    }

343
    QDBusConnectionInterface *interface = QDBusConnection::sessionBus().interface();
Nicolas Carion's avatar
Nicolas Carion committed
344
345
    if ((interface == nullptr) ||
        (!interface->isServiceRegistered(QStringLiteral("org.kde.ksmserver")) && !interface->isServiceRegistered(QStringLiteral("org.gnome.SessionManager")))) {
346
        m_view.shutdown->setEnabled(false);
347
    }
348
349
350
351

#ifdef KF5_USE_PURPOSE
    m_shareMenu = new Purpose::Menu();
    m_view.shareButton->setMenu(m_shareMenu);
352
    m_view.shareButton->setIcon(QIcon::fromTheme(QStringLiteral("document-share")));
353
    connect(m_shareMenu, &Purpose::Menu::finished, this, &RenderWidget::slotShareActionFinished);
354
355
356
#else
    m_view.shareButton->setEnabled(false);
#endif
357
    m_view.parallel_process->setChecked(KdenliveSettings::parallelrender());
358
    connect(m_view.parallel_process, &QCheckBox::stateChanged, [](int state) { KdenliveSettings::setParallelrender(state == Qt::Checked); });
359
360
361
362
    if (KdenliveSettings::gpu_accel()) {
        // Disable parallel rendering for movit
        m_view.parallel_process->setEnabled(false);
    }
363
    m_view.field_order->setEnabled(false);
364
    connect(m_view.scanning_list, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) { m_view.field_order->setEnabled(index == 2); });
365
    refreshView();
366
    focusFirstVisibleItem();
367
    adjustSize();
368
369
}

370
371
void RenderWidget::slotShareActionFinished(const QJsonObject &output, int error, const QString &message)
{
372
#ifdef KF5_USE_PURPOSE
373
374
    m_jobInfoMessage->hide();
    if (error) {
375
        KMessageBox::error(this, i18n("There was a problem sharing the document: %1", message), i18n("Share"));
376
377
378
379
380
381
382
    } else {
        const QString url = output["url"].toString();
        if (url.isEmpty()) {
            m_jobInfoMessage->setMessageType(KMessageWidget::Positive);
            m_jobInfoMessage->setText(i18n("Document shared successfully"));
            m_jobInfoMessage->show();
        } else {
383
            KMessageBox::information(this, i18n("You can find the shared document at: <a href=\"%1\">%1</a>", url), i18n("Share"), QString(),
384
385
386
                                     KMessageBox::Notify | KMessageBox::AllowLink);
        }
    }
Nicolas Carion's avatar
Nicolas Carion committed
387
388
389
390
#else
    Q_UNUSED(output);
    Q_UNUSED(error);
    Q_UNUSED(message);
391
#endif
392
}
393

394
395
396
QSize RenderWidget::sizeHint() const
{
    // Make sure the widget has minimum size on opening
Nicolas Carion's avatar
Nicolas Carion committed
397
    return {200, 200};
398
399
}

400
401
402
403
404
405
406
407
RenderWidget::~RenderWidget()
{
    m_view.running_jobs->blockSignals(true);
    m_view.scripts_list->blockSignals(true);
    m_view.running_jobs->clear();
    m_view.scripts_list->clear();
    delete m_jobsDelegate;
    delete m_scriptsDelegate;
408
    delete m_infoMessage;
409
    delete m_jobInfoMessage;
410
411
}

412
void RenderWidget::slotEditItem(QTreeWidgetItem *item)
413
{
Laurent Montel's avatar
Laurent Montel committed
414
    if (item->parent() == nullptr) {
415
416
417
        // This is a top level item - group - don't edit
        return;
    }
418
    const QString edit = item->data(0, EditableRole).toString();
419
    if (edit.isEmpty() || !edit.endsWith(QLatin1String("customprofiles.xml"))) {
Laurent Montel's avatar
Laurent Montel committed
420
        slotSaveProfile();
421
422
423
    } else {
        slotEditProfile();
    }
424
425
}

426
427
void RenderWidget::showInfoPanel()
{
428
429
430
431
432
433
434
    if (m_view.advanced_params->isVisible()) {
        m_view.advanced_params->setVisible(false);
        KdenliveSettings::setShowrenderparams(false);
    } else {
        m_view.advanced_params->setVisible(true);
        KdenliveSettings::setShowrenderparams(true);
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
435
436
}

437
void RenderWidget::updateDocumentPath()
438
{
439
440
    if (m_view.out_file->url().isEmpty()) {
        return;
441
    }
442
443
    const QString fileName = m_view.out_file->url().fileName();
    m_view.out_file->setUrl(QUrl::fromLocalFile(QDir(pCore->currentDoc()->projectDataFolder()).absoluteFilePath(fileName)));
444
    parseScriptFiles();
445
446
}

447
448
void RenderWidget::slotUpdateGuideBox()
{
449
450
451
    m_view.guides_box->setVisible(m_view.render_guide->isChecked());
}

452
453
void RenderWidget::slotCheckStartGuidePosition()
{
454
    if (m_view.guide_start->currentIndex() > m_view.guide_end->currentIndex()) {
455
        m_view.guide_start->setCurrentIndex(m_view.guide_end->currentIndex());
456
    }
457
458
}

459
460
void RenderWidget::slotCheckEndGuidePosition()
{
461
    if (m_view.guide_end->currentIndex() < m_view.guide_start->currentIndex()) {
462
        m_view.guide_end->setCurrentIndex(m_view.guide_start->currentIndex());
463
    }
464
465
}

466
void RenderWidget::setGuides(std::weak_ptr<MarkerListModel> guidesModel)
467
{
468
469
470
471
472
473
474
475
476
477
478
479
    m_guidesModel = std::move(guidesModel);
    reloadGuides();
    if (auto ptr = m_guidesModel.lock()) {
        connect(ptr.get(), &MarkerListModel::modelChanged, this, &RenderWidget::reloadGuides);
    }
}

void RenderWidget::reloadGuides()
{
    double projectDuration = GenTime(pCore->projectDuration() - TimelineModel::seekDuration - 2, pCore->getCurrentFps()).ms() / 1000;
    QVariant startData = m_view.guide_start->currentData();
    QVariant endData = m_view.guide_end->currentData();
480
481
    m_view.guide_start->clear();
    m_view.guide_end->clear();
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
    if (auto ptr = m_guidesModel.lock()) {
        QList<CommentedTime> markers = ptr->getAllMarkers();
        double fps = pCore->getCurrentProfile()->fps();
        m_view.render_guide->setEnabled(!markers.isEmpty());
        if (!markers.isEmpty()) {
            m_view.guide_start->addItem(i18n("Beginning"), "0");
            m_view.create_chapter->setEnabled(true);
            for (auto marker : markers) {
                GenTime pos = marker.time();
                const QString guidePos = Timecode::getStringTimecode(pos.frames(fps), fps);
                m_view.guide_start->addItem(marker.comment() + QLatin1Char('/') + guidePos, pos.seconds());
                m_view.guide_end->addItem(marker.comment() + QLatin1Char('/') + guidePos, pos.seconds());
            }
            m_view.guide_end->addItem(i18n("End"), QString::number(projectDuration));
            if (!startData.isNull()) {
                int ix = qMax(0, m_view.guide_start->findData(startData));
                m_view.guide_start->setCurrentIndex(ix);
            }
            if (!endData.isNull()) {
                int ix = qMax(m_view.guide_start->currentIndex() + 1, m_view.guide_end->findData(endData));
                m_view.guide_end->setCurrentIndex(ix);
            }
        }
505
506
507
508
    } else {
        m_view.render_guide->setEnabled(false);
        m_view.create_chapter->setEnabled(false);
    }
509
510
}

511
/**
Simon Eugster's avatar
Simon Eugster committed
512
513
514
 * Will be called when the user selects an output file via the file dialog.
 * File extension will be added automatically.
 */
515
void RenderWidget::slotUpdateButtons(const QUrl &url)
516
{
517
518
519
520
    if (m_view.out_file->url().isEmpty()) {
        m_view.buttonGenerateScript->setEnabled(false);
        m_view.buttonRender->setEnabled(false);
    } else {
521
522
        updateButtons(); // This also checks whether the selected format is available
    }
523
    if (url.isValid()) {
524
        QTreeWidgetItem *item = m_view.formats->currentItem();
Nicolas Carion's avatar
Nicolas Carion committed
525
        if ((item == nullptr) || (item->parent() == nullptr)) { // categories have no parent
526
527
            m_view.buttonRender->setEnabled(false);
            m_view.buttonGenerateScript->setEnabled(false);
528
529
            return;
        }
530
        const QString extension = item->data(0, ExtensionRole).toString();
Laurent Montel's avatar
Laurent Montel committed
531
        m_view.out_file->setUrl(filenameWithExtension(url, extension));
532
533
534
    }
}

Simon Eugster's avatar
Simon Eugster committed
535
536
537
538
/**
 * Will be called when the user changes the output file path in the text line.
 * File extension must NOT be added, would make editing impossible!
 */
539
540
void RenderWidget::slotUpdateButtons()
{
541
542
543
544
545
546
    if (m_view.out_file->url().isEmpty()) {
        m_view.buttonRender->setEnabled(false);
        m_view.buttonGenerateScript->setEnabled(false);
    } else {
        updateButtons(); // This also checks whether the selected format is available
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
547
548
}

549
550
void RenderWidget::slotSaveProfile()
{
551
    Ui::SaveProfile_UI ui;
552
    QPointer<QDialog> d = new QDialog(this);
553
    ui.setupUi(d);
554

555
    QString customGroup;
556
    QStringList arguments = m_view.advanced_params->toPlainText().split(' ', QString::SkipEmptyParts);
557
    if (!arguments.isEmpty()) {
558
        ui.parameters->setText(arguments.join(QLatin1Char(' ')));
559
    }
560
    ui.profile_name->setFocus();
561
    QTreeWidgetItem *item = m_view.formats->currentItem();
Nicolas Carion's avatar
Nicolas Carion committed
562
    if ((item != nullptr) && (item->parent() != nullptr)) { // not a category
563
        // Duplicate current item settings
564
565
        customGroup = item->parent()->text(0);
        ui.extension->setText(item->data(0, ExtensionRole).toString());
566
567
        if (ui.parameters->toPlainText().contains(QStringLiteral("%bitrate")) || ui.parameters->toPlainText().contains(QStringLiteral("%quality"))) {
            if (ui.parameters->toPlainText().contains(QStringLiteral("%quality"))) {
568
569
570
571
572
573
                ui.vbitrates_label->setText(i18n("Qualities"));
                ui.default_vbitrate_label->setText(i18n("Default quality"));
            } else {
                ui.vbitrates_label->setText(i18n("Bitrates"));
                ui.default_vbitrate_label->setText(i18n("Default bitrate"));
            }
Nicolas Carion's avatar
Nicolas Carion committed
574
            if (item->data(0, BitratesRole).canConvert(QVariant::StringList) && (item->data(0, BitratesRole).toStringList().count() != 0)) {
575
                QStringList bitrates = item->data(0, BitratesRole).toStringList();
576
                ui.vbitrates_list->setText(bitrates.join(QLatin1Char(',')));
577
                if (item->data(0, DefaultBitrateRole).canConvert(QVariant::String)) {
578
                    ui.default_vbitrate->setValue(item->data(0, DefaultBitrateRole).toInt());
579
                }
580
            }
581
582
        } else {
            ui.vbitrates->setHidden(true);
583
        }
584
585
        if (ui.parameters->toPlainText().contains(QStringLiteral("%audiobitrate")) || ui.parameters->toPlainText().contains(QStringLiteral("%audioquality"))) {
            if (ui.parameters->toPlainText().contains(QStringLiteral("%audioquality"))) {
586
587
588
589
590
591
                ui.abitrates_label->setText(i18n("Qualities"));
                ui.default_abitrate_label->setText(i18n("Default quality"));
            } else {
                ui.abitrates_label->setText(i18n("Bitrates"));
                ui.default_abitrate_label->setText(i18n("Default bitrate"));
            }
Nicolas Carion's avatar
Nicolas Carion committed
592
593
            if ((item != nullptr) && item->data(0, AudioBitratesRole).canConvert(QVariant::StringList) &&
                (item->data(0, AudioBitratesRole).toStringList().count() != 0)) {
594
                QStringList bitrates = item->data(0, AudioBitratesRole).toStringList();
595
                ui.abitrates_list->setText(bitrates.join(QLatin1Char(',')));
596
                if (item->data(0, DefaultAudioBitrateRole).canConvert(QVariant::String)) {
597
                    ui.default_abitrate->setValue(item->data(0, DefaultAudioBitrateRole).toInt());
598
                }
599
            }
600
601
        } else {
            ui.abitrates->setHidden(true);
602
        }
603

Nicolas Carion's avatar
Nicolas Carion committed
604
        if (item->data(0, SpeedsRole).canConvert(QVariant::StringList) && (item->data(0, SpeedsRole).toStringList().count() != 0)) {
605
606
607
            QStringList speeds = item->data(0, SpeedsRole).toStringList();
            ui.speeds_list->setText(speeds.join('\n'));
        }
608
    }
609

610
611
612
    if (customGroup.isEmpty()) {
        customGroup = i18nc("Group Name", "Custom");
    }
613
    ui.group_name->setText(customGroup);
614

615
    if (d->exec() == QDialog::Accepted && !ui.profile_name->text().simplified().isEmpty()) {
616
617
        QString newProfileName = ui.profile_name->text().simplified();
        QString newGroupName = ui.group_name->text().simplified();
618
619
620
        if (newGroupName.isEmpty()) {
            newGroupName = i18nc("Group Name", "Custom");
        }
621
622

        QDomDocument doc;
623
624
625
626
        QDomElement profileElement = doc.createElement(QStringLiteral("profile"));
        profileElement.setAttribute(QStringLiteral("name"), newProfileName);
        profileElement.setAttribute(QStringLiteral("category"), newGroupName);
        profileElement.setAttribute(QStringLiteral("extension"), ui.extension->text().simplified());
627
        QString args = ui.parameters->toPlainText().simplified();
628
629
        profileElement.setAttribute(QStringLiteral("args"), args);
        if (args.contains(QStringLiteral("%bitrate"))) {
630
            // profile has a variable bitrate
631
632
633
634
635
            profileElement.setAttribute(QStringLiteral("defaultbitrate"), QString::number(ui.default_vbitrate->value()));
            profileElement.setAttribute(QStringLiteral("bitrates"), ui.vbitrates_list->text());
        } else if (args.contains(QStringLiteral("%quality"))) {
            profileElement.setAttribute(QStringLiteral("defaultquality"), QString::number(ui.default_vbitrate->value()));
            profileElement.setAttribute(QStringLiteral("qualities"), ui.vbitrates_list->text());
636
        }
637

638
        if (args.contains(QStringLiteral("%audiobitrate"))) {
639
            // profile has a variable bitrate
640
641
642
            profileElement.setAttribute(QStringLiteral("defaultaudiobitrate"), QString::number(ui.default_abitrate->value()));
            profileElement.setAttribute(QStringLiteral("audiobitrates"), ui.abitrates_list->text());
        } else if (args.contains(QStringLiteral("%audioquality"))) {
643
            // profile has a variable bitrate
644
645
            profileElement.setAttribute(QStringLiteral("defaultaudioquality"), QString::number(ui.default_abitrate->value()));
            profileElement.setAttribute(QStringLiteral("audioqualities"), ui.abitrates_list->text());
646
        }
647
648
649
        QString speeds_list_str = ui.speeds_list->toPlainText();
        if (!speeds_list_str.isEmpty()) {
            profileElement.setAttribute(QStringLiteral("speeds"), speeds_list_str.replace('\n', ';').simplified());
650
        }
651

652
653
654
        doc.appendChild(profileElement);
        saveProfile(doc.documentElement());

655
        parseProfiles();
656
657
658
659
    }
    delete d;
}

660
bool RenderWidget::saveProfile(QDomElement newprofile)
661
{
Laurent Montel's avatar
Laurent Montel committed
662
    QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/export/"));
663
    if (!dir.exists()) {
664
        dir.mkpath(QStringLiteral("."));
665
    }
666
    QDomDocument doc;
667
    QFile file(dir.absoluteFilePath(QStringLiteral("customprofiles.xml")));
668
669
670
671
    doc.setContent(&file, false);
    file.close();
    QDomElement documentElement;
    QDomElement profiles = doc.documentElement();
672
    if (profiles.isNull() || profiles.tagName() != QLatin1String("profiles")) {
673
        doc.clear();
674
675
        profiles = doc.createElement(QStringLiteral("profiles"));
        profiles.setAttribute(QStringLiteral("version"), 1);
676
677
        doc.appendChild(profiles);
    }
Laurent Montel's avatar
Laurent Montel committed
678
    int version = profiles.attribute(QStringLiteral("version"), nullptr).toInt();
679
680
    if (version < 1) {
        doc.clear();
681
682
        profiles = doc.createElement(QStringLiteral("profiles"));
        profiles.setAttribute(QStringLiteral("version"), 1);
683
684
        doc.appendChild(profiles);
    }
685

686
    QDomNodeList profilelist = doc.elementsByTagName(QStringLiteral("profile"));
687
688
689
    QString newProfileName = newprofile.attribute(QStringLiteral("name"));
    // Check existing profiles
    QStringList existingProfileNames;
690
691
692
    int i = 0;
    while (!profilelist.item(i).isNull()) {
        documentElement = profilelist.item(i).toElement();
693
        QString profileName = documentElement.attribute(QStringLiteral("name"));
694
695
696
697
698
699
        existingProfileNames << profileName;
        i++;
    }
    // Check if a profile with that same name already exists
    bool ok;
    while (existingProfileNames.contains(newProfileName)) {
Nicolas Carion's avatar
Nicolas Carion committed
700
        QString updatedProfileName = QInputDialog::getText(this, i18n("Profile already exists"),
Pino Toscano's avatar
Pino Toscano committed
701
                                                           i18n("This profile name already exists. Change the name if you do not want to overwrite it."),
Nicolas Carion's avatar
Nicolas Carion committed
702
                                                           QLineEdit::Normal, newProfileName, &ok);
703
704
705
        if (!ok) {
            return false;
        }
706
707
708
709
710
711
712
        if (updatedProfileName == newProfileName) {
            // remove previous profile
            profiles.removeChild(profilelist.item(existingProfileNames.indexOf(newProfileName)));
            break;
        } else {
            newProfileName = updatedProfileName;
            newprofile.setAttribute(QStringLiteral("name"), newProfileName);
713
        }
714
    }
715

716
    profiles.appendChild(newprofile);
717

Nicolas Carion's avatar
Nicolas Carion committed
718
    // QCString save = doc.toString().utf8();
719

720
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
721
        KMessageBox::sorry(this, i18n("Unable to write to file %1", dir.absoluteFilePath("customprofiles.xml")));
722
        return false;
723
724
725
726
    }
    QTextStream out(&file);
    out << doc.toString();
    if (file.error() != QFile::NoError) {
727
        KMessageBox::error(this, i18n("Cannot write to file %1", dir.absoluteFilePath("customprofiles.xml")));
728
        file.close();
729
        return false;
730
731
    }
    file.close();
732
    return true;
733
734
735
736
}

void RenderWidget::slotCopyToFavorites()
{
737
    QTreeWidgetItem *item = m_view.formats->currentItem();
Nicolas Carion's avatar
Nicolas Carion committed
738
    if ((item == nullptr) || (item->parent() == nullptr)) {
Laurent Montel's avatar
Laurent Montel committed
739
        return;
740
    }
741

742
743
744
    QString params = item->data(0, ParamsRole).toString();
    QString extension = item->data(0, ExtensionRole).toString();
    QString currentProfile = item->text(0);
745
    QDomDocument doc;
746
747
748
749
750
751
752
    QDomElement profileElement = doc.createElement(QStringLiteral("profile"));
    profileElement.setAttribute(QStringLiteral("name"), currentProfile);
    profileElement.setAttribute(QStringLiteral("category"), i18nc("Category Name", "Custom"));
    profileElement.setAttribute(QStringLiteral("destinationid"), QStringLiteral("favorites"));
    profileElement.setAttribute(QStringLiteral("extension"), extension);
    profileElement.setAttribute(QStringLiteral("args"), params);
    if (params.contains(QStringLiteral("%bitrate"))) {
753
        // profile has a variable bitrate
754
        profileElement.setAttribute(QStringLiteral("defaultbitrate"), item->data(0, DefaultBitrateRole).toString());
755
        profileElement.setAttribute(QStringLiteral("bitrates"), item->data(0, BitratesRole).toStringList().join(QLatin1Char(',')));
756
    } else if (params.contains(QStringLiteral("%quality"))) {
757
        profileElement.setAttribute(QStringLiteral("defaultquality"), item->data(0, DefaultBitrateRole).toString());
758
        profileElement.setAttribute(QStringLiteral("qualities"), item->data(0, BitratesRole).toStringList().join(QLatin1Char(',')));
759
    }
760
    if (params.contains(QStringLiteral("%audiobitrate"))) {
761
        // profile has a variable bitrate
762
        profileElement.setAttribute(QStringLiteral("defaultaudiobitrate"), item->data(0, DefaultAudioBitrateRole).toString());
763
        profileElement.setAttribute(QStringLiteral("audiobitrates"), item->data(0, AudioBitratesRole).toStringList().join(QLatin1Char(',')));
764
    } else if (params.contains(QStringLiteral("%audioquality"))) {
765
        // profile has a variable bitrate
766
        profileElement.setAttribute(QStringLiteral("defaultaudioquality"), item->data(0, DefaultAudioBitrateRole).toString());
767
        profileElement.setAttribute(QStringLiteral("audioqualities"), item->data(0, AudioBitratesRole).toStringList().join(QLatin1Char(',')));
768
    }
Nicolas Carion's avatar
Nicolas Carion committed
769
    if (item->data(0, SpeedsRole).canConvert(QVariant::StringList) && (item->data(0, SpeedsRole).toStringList().count() != 0)) {
770
        // profile has a variable speed
771
        profileElement.setAttribute(QStringLiteral("speeds"), item->data(0, SpeedsRole).toStringList().join(QLatin1Char(';')));
772
    }
773
    doc.appendChild(profileElement);
774
    if (saveProfile(doc.documentElement())) {
775
        parseProfiles(profileElement.attribute(QStringLiteral("name")));
776
    }