Krita Source Code Documentation
Loading...
Searching...
No Matches
recorder_export.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Dmitrii Utkin <loentar@gmail.com>
3 *
4 * SPDX-License-Identifier: LGPL-2.1-only
5 */
6
7#include "recorder_export.h"
8#include "ui_recorder_export.h"
14
15#include <klocalizedstring.h>
16#include <kis_icon_utils.h>
17#include "kis_config.h"
18#include "KoFileDialog.h"
19#include "KisMimeDatabase.h"
20
21#include <QAction>
22#include <QDesktopServices>
23#include <QDir>
24#include <QDirIterator>
25#include <QUrl>
26#include <QDebug>
27#include <QCloseEvent>
28#include <QMessageBox>
29#include <QJsonObject>
30#include <QJsonArray>
31#include <QImageReader>
32#include <QElapsedTimer>
33
34#include "kis_debug.h"
35
36
37namespace
38{
39enum ExportPageIndex
40{
41 PageSettings = 0,
42 PageProgress = 1,
43 PageDone = 2
44};
45}
46
47
49{
50public:
52 QScopedPointer<Ui::RecorderExport> ui;
54
55 QScopedPointer<KisFFMpegWrapper> ffmpeg;
57
58 QElapsedTimer elapsedTimer;
59
62
64 : q(q_ptr)
65 , ui(new Ui::RecorderExport)
66 , settings(q_ptr->settings)
67 {
68 }
69
71 {
72 const QJsonObject ffmpegJson = KisFFMpegWrapper::findFFMpeg(settings->ffmpegPath);
73 const bool success = ffmpegJson["enabled"].toBool();
74 const QIcon &icon = KisIconUtils::loadIcon(success ? "dialog-ok" : "window-close");
75 const QList<QAction *> &actions = ui->editFfmpegPath->actions();
76 QAction *action;
77
78 if (!actions.isEmpty()) {
79 action = actions.first();
80 action->setIcon(icon);
81 } else {
82 action = ui->editFfmpegPath->addAction(icon, QLineEdit::TrailingPosition);
83 }
84 if (success) {
85 const QJsonArray h264Encoders = ffmpegJson["codecs"].toObject()["h264"].toObject()["encoders"].toArray();
86 settings->ffmpegPath = ffmpegJson["path"].toString();
87 settings->h264Encoder = h264Encoders.contains("libopenh264") ? "libopenh264" : "libx264";
88 ui->editFfmpegPath->setText(settings->ffmpegPath);
89 action->setToolTip("Version: "+ffmpegJson["version"].toString()
90 +(ffmpegJson["codecs"].toObject()["h264"].toObject()["encoding"].toBool() ? "":" (MP4/MKV UNSUPPORTED)")
91 );
92 } else {
93 ui->editFfmpegPath->setText(i18nc("This text is displayed instead of path to external tool in case of external tool is not found", "[NOT FOUND]"));
94 action->setToolTip(i18n("FFmpeg executable location couldn't be detected, please install it or select its location manually"));
95 }
96 ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(success);
97 }
98
100 {
101 QSignalBlocker blocker(ui->comboProfile);
102 ui->comboProfile->clear();
103 for (const RecorderProfile &profile : settings->profiles) {
104 ui->comboProfile->addItem(profile.name);
105 }
106 blocker.unblock();
107 ui->comboProfile->setCurrentIndex(settings->profileIndex);
108 }
109
111 {
113 QDir::Name, QDir::Files | QDir::NoDotAndDotDot);
114 const QStringList &frames = dir.entryList(); // dir.count() calls entryList().count() internally
115 settings->framesCount = frames.count();
116 if (settings->framesCount != 0) {
117 const QString &fileName = settings->inputDirectory % QDir::separator() % frames.last();
118 settings->imageSize = QImageReader(fileName).size();
119 settings->imageSize.rwidth() &= ~1;
120 settings->imageSize.rheight() &= ~1;
121 }
122 }
123
125 {
126 if (settings->videoDirectory.isEmpty())
128
130 % QDir::separator()
132 % "."
133 % settings->profiles[settings->profileIndex].extension;
134 QSignalBlocker blocker(ui->editVideoFilePath);
135 ui->editVideoFilePath->setText(settings->videoFilePath);
136 }
137
138 void updateRatio(bool widthToHeight)
139 {
140 const float ratio = static_cast<float>(settings->imageSize.width()) / static_cast<float>(settings->imageSize.height());
141 if (widthToHeight) {
142 settings->size.setHeight(static_cast<int>(settings->size.width() / ratio));
143 } else {
144 settings->size.setWidth(static_cast<int>(settings->size.height() * ratio));
145 }
146 // make width and height even
147 settings->size.rwidth() &= ~1;
148 settings->size.rheight() &= ~1;
149 QSignalBlocker blockerWidth(ui->spinScaleHeight);
150 QSignalBlocker blockerHeight(ui->spinScaleWidth);
151 ui->spinScaleHeight->setValue(settings->size.height());
152 ui->spinScaleWidth->setValue(settings->size.width());
153 }
154
155 void updateFps(RecorderExportConfig &config, bool takeFromInputFps = false)
156 {
157 if (!settings->lockFps)
158 return;
159
160 if (takeFromInputFps) {
162 config.setFps(settings->fps);
163 ui->spinFps->setValue(settings->fps);
164 } else {
167 ui->spinInputFps->setValue(settings->inputFps);
168 }
170 }
171
173 {
174 if (!ffmpeg)
175 return true;
176
177 if (QMessageBox::question(q, q->windowTitle(), i18n("Abort encoding the timelapse video?"))
178 == QMessageBox::Yes) {
180 return true;
181 }
182
183 return false;
184 }
185
186 QStringList splitCommand(const QString &command)
187 {
188 QStringList args;
189 QString tmp;
190 int quoteCount = 0;
191 bool inQuote = false;
192
193 // handle quoting. tokens can be surrounded by double quotes
194 // "hello world". three consecutive double quotes represent
195 // the quote character itself.
196 for (int i = 0; i < command.size(); ++i) {
197 if (command.at(i) == QLatin1Char('"')) {
198 ++quoteCount;
199 if (quoteCount == 3) {
200 // third consecutive quote
201 quoteCount = 0;
202 tmp += command.at(i);
203 }
204 continue;
205 }
206 if (quoteCount) {
207 if (quoteCount == 1)
208 inQuote = !inQuote;
209 quoteCount = 0;
210 }
211 if (!inQuote && command.at(i).isSpace()) {
212 if (!tmp.isEmpty()) {
213 args += tmp;
214 tmp.clear();
215 }
216 } else {
217 tmp += command.at(i);
218 }
219 }
220 if (!tmp.isEmpty())
221 args += tmp;
222
223 return args;
224 }
225
227 {
228 Q_ASSERT(ffmpeg == nullptr);
229
231
232 const QString &arguments = applyVariables(settings->profiles[settings->profileIndex].arguments);
233
234 ffmpeg.reset(new KisFFMpegWrapper(q));
235 QObject::connect(ffmpeg.data(), SIGNAL(sigStarted()), q, SLOT(onFFMpegStarted()));
236 QObject::connect(ffmpeg.data(), SIGNAL(sigFinished()), q, SLOT(onFFMpegFinished()));
237 QObject::connect(ffmpeg.data(), SIGNAL(sigFinishedWithError(QString)), q, SLOT(onFFMpegFinishedWithError(QString)));
238 QObject::connect(ffmpeg.data(), SIGNAL(sigProgressUpdated(int)), q, SLOT(onFFMpegProgressUpdated(int)));
239
240 KisFFMpegWrapperSettings FFmpegSettings;
241 KisConfig cfg(true);
242 FFmpegSettings.processPath = settings->ffmpegPath;
243 FFmpegSettings.args = splitCommand(arguments);
244 FFmpegSettings.outputFile = settings->videoFilePath;
245 FFmpegSettings.batchMode = true; //TODO: Consider renaming to 'silent' mode, meaning no window for extra window handling...
246
247 ffmpeg->startNonBlocking(FFmpegSettings);
248 ui->labelStatus->setText(i18nc("Status for the export of the video record", "Starting FFmpeg..."));
249 ui->buttonCancelExport->setEnabled(false);
250 ui->progressExport->setValue(0);
251 elapsedTimer.start();
252 }
253
255 {
256 if (ffmpeg) {
257 ffmpeg->reset();
258 ffmpeg.reset();
259 }
260 }
261
262 QString applyVariables(const QString &templateArguments)
263 {
264 const QSize &outSize = settings->resize ? settings->size : settings->imageSize;
265 const int previewLength = settings->resultPreview ? settings->firstFrameSec : 0;
266 const int resultLength = settings->extendResult ? settings->lastFrameSec : 0;
267 const float transitionLength = settings->resultPreview ? 0.7 : 0;
268 return QString(templateArguments)
269 .replace("$IN_FPS", QString::number(settings->inputFps))
270 .replace("$OUT_FPS", QString::number(settings->fps))
271 .replace("$WIDTH", QString::number(outSize.width()))
272 .replace("$HEIGHT", QString::number(outSize.height()))
273 .replace("$FRAMES", QString::number(settings->framesCount))
274 .replace("$INPUT_DIR", settings->inputDirectory)
275 .replace("$FIRST_FRAME_SEC", QString::number(previewLength))
276 .replace("$TRANSITION_LENGTH", QString::number(transitionLength))
277 .replace("$H264_ENCODER", settings->h264Encoder)
278 .replace("$LAST_FRAME_SEC", QString::number(resultLength))
280 }
281
283 {
284 long ms = (settings->framesCount * 1000L / (settings->inputFps ? settings->inputFps : 30));
285
286 if (settings->resultPreview) {
287 ms += (settings->firstFrameSec * 1000L);
288 }
289
290 if (settings->extendResult) {
291 ms += (settings->lastFrameSec * 1000L);
292 }
293
294 ui->labelVideoDuration->setText(formatDuration(ms));
295 }
296
297 QString formatDuration(long durationMs)
298 {
299 QString result;
300 const long ms = (durationMs % 1000) / 10;
301
302 result += QString(".%1").arg(ms, 2, 10, QLatin1Char('0'));
303
304 long duration = durationMs / 1000;
305 const long seconds = duration % 60;
306 result = QString("%1%2").arg(seconds, 2, 10, QLatin1Char('0')).arg(result);
307
308 duration = duration / 60;
309 const long minutes = duration % 60;
310 if (minutes != 0) {
311 result = QString("%1:%2").arg(minutes, 2, 10, QLatin1Char('0')).arg(result);
312
313 duration = duration / 60;
314 if (duration != 0)
315 result = QString("%1:%2").arg(duration, 2, 10, QLatin1Char('0')).arg(result);
316 }
317
318 return result;
319 }
320};
321
322
324 : QDialog(parent)
325 , settings(s)
326 , d(new Private(this))
327{
328 d->ui->setupUi(this);
329 d->spinInputFPSMaxValue = d->ui->spinInputFps->minimum();
330 d->spinInputFPSMaxValue = d->ui->spinInputFps->maximum();
331 d->ui->buttonBrowseDirectory->setIcon(KisIconUtils::loadIcon("view-preview"));
332 d->ui->buttonBrowseFfmpeg->setIcon(KisIconUtils::loadIcon("folder"));
333 d->ui->buttonEditProfile->setIcon(KisIconUtils::loadIcon("document-edit"));
334 d->ui->buttonBrowseExport->setIcon(KisIconUtils::loadIcon("folder"));
335 d->ui->buttonLockRatio->setIcon(settings->lockRatio ? KisIconUtils::loadIcon("locked") : KisIconUtils::loadIcon("unlocked"));
336 d->ui->buttonLockFps->setIcon(settings->lockFps ? KisIconUtils::loadIcon("locked") : KisIconUtils::loadIcon("unlocked"));
337 d->ui->buttonWatchIt->setIcon(KisIconUtils::loadIcon("media-playback-start"));
338 d->ui->buttonShowInFolder->setIcon(KisIconUtils::loadIcon("folder"));
339 d->ui->buttonRemoveSnapshots->setIcon(KisIconUtils::loadIcon("edit-delete"));
340 d->ui->stackedWidget->setCurrentIndex(ExportPageIndex::PageSettings);
341 d->ui->spinLastFrameSec->setEnabled(d->ui->extendResultCheckBox->isChecked());
342 d->ui->spinFirstFrameSec->setEnabled(d->ui->resultPreviewCheckBox->isChecked());
343
344 connect(d->ui->buttonBrowseDirectory, SIGNAL(clicked()), SLOT(onButtonBrowseDirectoryClicked()));
345 connect(d->ui->spinInputFps, SIGNAL(valueChanged(int)), SLOT(onSpinInputFpsValueChanged(int)));
346 connect(d->ui->spinFps, SIGNAL(valueChanged(int)), SLOT(onSpinFpsValueChanged(int)));
347 connect(d->ui->resultPreviewCheckBox, SIGNAL(toggled(bool)), SLOT(onCheckResultPreviewToggled(bool)));
348 connect(d->ui->spinFirstFrameSec, SIGNAL(valueChanged(int)), SLOT(onFirstFrameSecValueChanged(int)));
349 connect(d->ui->extendResultCheckBox, SIGNAL(toggled(bool)), SLOT(onCheckExtendResultToggled(bool)));
350 connect(d->ui->spinLastFrameSec, SIGNAL(valueChanged(int)), SLOT(onLastFrameSecValueChanged(int)));
351 connect(d->ui->checkResize, SIGNAL(toggled(bool)), SLOT(onCheckResizeToggled(bool)));
352 connect(d->ui->spinScaleWidth, SIGNAL(valueChanged(int)), SLOT(onSpinScaleWidthValueChanged(int)));
353 connect(d->ui->spinScaleHeight, SIGNAL(valueChanged(int)), SLOT(onSpinScaleHeightValueChanged(int)));
354 connect(d->ui->buttonLockRatio, SIGNAL(toggled(bool)), SLOT(onButtonLockRatioToggled(bool)));
355 connect(d->ui->buttonLockFps, SIGNAL(toggled(bool)), SLOT(onButtonLockFpsToggled(bool)));
356 connect(d->ui->buttonBrowseFfmpeg, SIGNAL(clicked()), SLOT(onButtonBrowseFfmpegClicked()));
357 connect(d->ui->comboProfile, SIGNAL(currentIndexChanged(int)), SLOT(onComboProfileIndexChanged(int)));
358 connect(d->ui->buttonEditProfile, SIGNAL(clicked()), SLOT(onButtonEditProfileClicked()));
359 connect(d->ui->editVideoFilePath, SIGNAL(textChanged(QString)), SLOT(onEditVideoPathChanged(QString)));
360 connect(d->ui->buttonBrowseExport, SIGNAL(clicked()), SLOT(onButtonBrowseExportClicked()));
361 connect(d->ui->buttonBox->button(QDialogButtonBox::Save), SIGNAL(clicked()), this, SLOT(onButtonExportClicked()));
362 connect(d->ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
363 connect(d->ui->buttonCancelExport, SIGNAL(clicked()), SLOT(onButtonCancelClicked()));
364 connect(d->ui->buttonWatchIt, SIGNAL(clicked()), SLOT(onButtonWatchItClicked()));
365 connect(d->ui->buttonShowInFolder, SIGNAL(clicked()), SLOT(onButtonShowInFolderClicked()));
366 connect(d->ui->buttonRemoveSnapshots, SIGNAL(clicked()), SLOT(onButtonRemoveSnapshotsClicked()));
367 connect(d->ui->buttonRestart, SIGNAL(clicked()), SLOT(onButtonRestartClicked()));
368 connect(d->ui->resultPreviewCheckBox, SIGNAL(toggled(bool)), d->ui->spinFirstFrameSec, SLOT(setEnabled(bool)));
369 connect(d->ui->extendResultCheckBox, SIGNAL(toggled(bool)), d->ui->spinLastFrameSec, SLOT(setEnabled(bool)));
370
372 d->ui->buttonBox->button(QDialogButtonBox::Close)->setText("OK");
373 d->ui->buttonBox->button(QDialogButtonBox::Save)->setText(i18n("Export"));
374 d->ui->editVideoFilePath->installEventFilter(this);
375}
376
380
382{
383 RecorderExportConfig config(true);
384 d->updateFps(config);
385 d->updateFrameInfo();
386
387 if (settings->framesCount == 0) {
388 d->ui->labelRecordInfo->setText(i18nc("Can't export recording because nothing to export", "No frames to export"));
389 d->ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
390 } else {
391 d->ui->labelRecordInfo->setText(QString("%1: %2x%3 %4, %5 %6")
392 .arg(i18nc("General information about recording", "Recording info"))
393 .arg(settings->imageSize.width())
394 .arg(settings->imageSize.height())
395 .arg(i18nc("Pixel dimension suffix", "px"))
396 .arg(settings->framesCount)
397 .arg(i18nc("The suffix after number of frames", "frame(s)"))
398 );
399 }
400
401
402 // Don't load lockFps flag from config, if liveCaptureMode was just set by the user
405
406 d->ui->spinInputFps->setValue(settings->inputFps);
407 d->ui->spinFps->setValue(settings->fps);
408 d->ui->resultPreviewCheckBox->setChecked(settings->resultPreview);
409 d->ui->spinFirstFrameSec->setValue(settings->firstFrameSec);
410 d->ui->extendResultCheckBox->setChecked(settings->extendResult);
411 d->ui->spinLastFrameSec->setValue(settings->lastFrameSec);
412 d->ui->checkResize->setChecked(settings->resize);
413 d->ui->spinScaleWidth->setValue(settings->size.width());
414 d->ui->spinScaleHeight->setValue(settings->size.height());
415 d->ui->buttonLockRatio->setChecked(settings->lockRatio);
416 d->ui->buttonLockRatio->setIcon(settings->lockRatio ? KisIconUtils::loadIcon("locked") : KisIconUtils::loadIcon("unlocked"));
417 d->ui->labelRealTimeCaptureNotion->setVisible(settings->realTimeCaptureMode);
418 d->ui->buttonLockFps->setChecked(settings->lockFps);
419 d->ui->buttonLockFps->setIcon(settings->lockFps ? KisIconUtils::loadIcon("locked") : KisIconUtils::loadIcon("unlocked"));
420 d->fillComboProfiles();
421 d->checkFfmpeg();
422 d->updateVideoFilePath();
423 d->updateVideoDuration();
424}
425
426void RecorderExport::closeEvent(QCloseEvent *event)
427{
428 if (!d->tryAbortExport())
429 event->ignore();
430}
431
433{
434 if (d->tryAbortExport())
435 QDialog::reject();
436}
437
439{
440 if (settings->framesCount != 0) {
441 QDesktopServices::openUrl(QUrl::fromLocalFile(settings->inputDirectory));
442 } else {
443 QMessageBox::warning(this, windowTitle(), i18nc("Can't browse frames of recording because no frames have been recorded", "No frames to browse."));
444 return;
445 }
446}
447
449{
451 RecorderExportConfig config(false);
452 config.setInputFps(value);
453 d->updateFps(config, true);
454 d->updateVideoDuration();
455}
456
458{
459 settings->fps = value;
460 RecorderExportConfig config(false);
461 config.setFps(value);
462 d->updateFps(config, false);
463 d->updateVideoDuration();
464}
465
467{
468 settings->resultPreview = checked;
470 d->updateVideoDuration();
471}
472
479
481{
482 settings->extendResult = checked;
484 d->updateVideoDuration();
485}
486
488{
491 d->updateVideoDuration();
492}
493
495{
496 settings->resize = checked;
497 RecorderExportConfig(false).setResize(checked);
498}
499
501{
502 settings->size.setWidth(value);
503 if (settings->lockRatio)
504 d->updateRatio(true);
506}
507
509{
510 settings->size.setHeight(value);
511 if (settings->lockRatio)
512 d->updateRatio(false);
514}
515
517{
518 settings->lockRatio = checked;
519 RecorderExportConfig config(false);
520 config.setLockRatio(checked);
521 if (settings->lockRatio) {
522 d->updateRatio(true);
523 config.setSize(settings->size);
524 }
525 d->ui->buttonLockRatio->setIcon(settings->lockRatio ? KisIconUtils::loadIcon("locked") : KisIconUtils::loadIcon("unlocked"));
526}
527
529{
530 settings->lockFps = checked;
531 RecorderExportConfig config(false);
532 config.setLockFps(checked);
533 d->updateFps(config);
534 if (settings->lockFps) {
535 d->ui->buttonLockFps->setIcon(KisIconUtils::loadIcon("locked"));
536 d->ui->spinInputFps->setMinimum(d->ui->spinFps->minimum());
537 d->ui->spinInputFps->setMaximum(d->ui->spinFps->maximum());
538 } else {
539 d->ui->buttonLockFps->setIcon(KisIconUtils::loadIcon("unlocked"));
540 d->ui->spinInputFps->setMinimum(d->spinInputFPSMinValue);
541 d->ui->spinInputFps->setMaximum(d->spinInputFPSMaxValue);
542 }
543
544}
545
547{
548 KoFileDialog dialog(this, KoFileDialog::OpenFile, "SelectFFmpeg");
549 dialog.setCaption(i18n("Select FFmpeg Executable File"));
550 dialog.setDefaultDir(settings->ffmpegPath);
551 QString file = dialog.filename();
552 if (!file.isEmpty()) {
553 settings->ffmpegPath = file;
555 d->checkFfmpeg();
556 }
557}
558
560{
561 settings->profileIndex = index;
562 d->updateVideoFilePath();
564}
565
567{
568 RecorderProfileSettings settingsDialog(this);
569
570 connect(&settingsDialog, &RecorderProfileSettings::requestPreview, [&](const QString & arguments) {
571 settingsDialog.setPreview(settings->ffmpegPath % " -y " % d->applyVariables(arguments).replace("\n", " ")
572 % " \"" % settings->videoFilePath % "\"");
573 });
574
575 if (settingsDialog.editProfile(
577 d->fillComboProfiles();
578 d->updateVideoFilePath();
580 }
581}
582
583void RecorderExport::onEditVideoPathChanged(const QString &videoFilePath)
584{
585 QFileInfo fileInfo(videoFilePath);
586 if (!fileInfo.isRelative())
587 settings->videoDirectory = fileInfo.absolutePath();
588 settings->videoFileName = fileInfo.completeBaseName();
589}
590
592{
593 KoFileDialog dialog(this, KoFileDialog::SaveFile, "ExportTimelapse");
594 dialog.setCaption(i18n("Export Timelapse Video As"));
595 dialog.setDefaultDir(settings->videoDirectory);
596 const QString &extension = settings->profiles[settings->profileIndex].extension;
597 dialog.setMimeTypeFilters(QStringList(KisMimeDatabase::mimeTypeForSuffix(extension)));
598 QString videoFileName = dialog.filename();
599 if (!videoFileName.isEmpty()) {
600 QFileInfo fileInfo(videoFileName);
601 settings->videoDirectory = fileInfo.absolutePath();
602 settings->videoFileName = fileInfo.completeBaseName();
603 d->updateVideoFilePath();
605 }
606}
607
609{
610 if (QFile::exists(settings->videoFilePath)) {
611 if (settings->framesCount != 0) {
612 if (QMessageBox::question(this, windowTitle(),
613 i18n("The video file already exists. Do you wish to overwrite it?"))
614 != QMessageBox::Yes) {
615 return;
616 }
617 } else {
618 QMessageBox::warning(this, windowTitle(), i18n("No frames to export."));
619 return;
620 }
621 }
622
623
624 d->ui->stackedWidget->setCurrentIndex(ExportPageIndex::PageProgress);
625 d->startExport();
626}
627
629{
630 if (d->cleaner) {
631 d->cleaner->stop();
632 d->cleaner->deleteLater();
633 d->cleaner = nullptr;
634 return;
635 }
636
637 if (d->tryAbortExport())
638 d->ui->stackedWidget->setCurrentIndex(ExportPageIndex::PageSettings);
639}
640
641
643{
644 d->ui->buttonCancelExport->setEnabled(true);
645 d->ui->labelStatus->setText(i18n("The timelapse video is being encoded..."));
646}
647
649{
650 quint64 elapsed = d->elapsedTimer.elapsed();
651 d->ui->labelRenderTime->setText(d->formatDuration(elapsed));
652 d->ui->stackedWidget->setCurrentIndex(ExportPageIndex::PageDone);
653 d->ui->labelVideoPathDone->setText(settings->videoFilePath);
654 d->cleanupFFMpeg();
655}
656
658{
659 d->ui->stackedWidget->setCurrentIndex(ExportPageIndex::PageSettings);
660 QMessageBox::critical(this, windowTitle(), i18n("Export failed. FFmpeg message:") % "\n\n" % error);
661 d->cleanupFFMpeg();
662}
663
665{
666 d->ui->progressExport->setValue(frameNo * 100 / (settings->framesCount * settings->fps / static_cast<float>(settings->inputFps)));
667}
668
670{
671 QDesktopServices::openUrl(QUrl::fromLocalFile(settings->videoFilePath));
672}
673
675{
676 QDesktopServices::openUrl(QUrl::fromLocalFile(settings->videoDirectory));
677}
678
680{
681 const QString confirmation(i18n("The recordings for this document will be deleted"
682 " and you will not be able to export a timelapse for it again"
683 ". Note that already exported timelapses will still be preserved."
684 "\n\nDo you wish to continue?"));
685 if (QMessageBox::question(this, windowTitle(), confirmation) != QMessageBox::Yes)
686 return;
687
688 d->ui->labelStatus->setText(i18nc("Label title, Snapshot directory deleting is in progress", "Cleaning up..."));
689 d->ui->stackedWidget->setCurrentIndex(ExportPageIndex::PageProgress);
690
691 Q_ASSERT(d->cleaner == nullptr);
692 d->cleaner = new RecorderDirectoryCleaner({d->settings->inputDirectory});
693 connect(d->cleaner, SIGNAL(finished()), this, SLOT(onCleanUpFinished()));
694 d->cleaner->start();
695}
696
698{
699 d->ui->stackedWidget->setCurrentIndex(ExportPageIndex::PageSettings);
700}
701
703{
704 d->cleaner->deleteLater();
705 d->cleaner = nullptr;
706
707 d->ui->stackedWidget->setCurrentIndex(ExportPageIndex::PageDone);
708 d->ui->buttonRestart->hide();
709 d->ui->buttonRemoveSnapshots->hide();
710}
711
712bool RecorderExport::eventFilter(QObject *obj, QEvent *event)
713{
714 if (obj == d->ui->editVideoFilePath && event->type() == QEvent::FocusOut)
715 d->updateVideoFilePath();
716
717 return QDialog::eventFilter(obj, event);
718}
float value(const T *src, size_t ch)
QList< QString > QStringList
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
static QJsonObject findFFMpeg(const QString &customLocation)
static QString mimeTypeForSuffix(const QString &suffix)
Find the mimetype for a given extension. The extension may have the form "*.xxx" or "xxx".
void setProfiles(const QList< RecorderProfile > &value)
void setVideoDirectory(const QString &value)
void loadConfiguration(RecorderExportSettings *settings, bool loadLockFps=true) const
void setFfmpegPath(const QString &value)
void setSize(const QSize &value)
void updateFps(RecorderExportConfig &config, bool takeFromInputFps=false)
RecorderDirectoryCleaner * cleaner
QString applyVariables(const QString &templateArguments)
QScopedPointer< Ui::RecorderExport > ui
RecorderExportSettings * settings
QString formatDuration(long durationMs)
Private(RecorderExport *q_ptr)
QScopedPointer< KisFFMpegWrapper > ffmpeg
QStringList splitCommand(const QString &command)
void updateRatio(bool widthToHeight)
void onButtonBrowseFfmpegClicked()
void onCheckResultPreviewToggled(bool checked)
void onSpinInputFpsValueChanged(int value)
void onLastFrameSecValueChanged(int value)
void onButtonShowInFolderClicked()
void closeEvent(QCloseEvent *event) override
void onEditVideoPathChanged(const QString &videoFilePath)
RecorderExportSettings * settings
RecorderExport(RecorderExportSettings *s, QWidget *parent=nullptr)
void onFFMpegFinishedWithError(QString error)
void onSpinScaleHeightValueChanged(int value)
void onButtonEditProfileClicked()
void onFirstFrameSecValueChanged(int value)
void onButtonRemoveSnapshotsClicked()
QScopedPointer< Private > d
void reject() override
void onCheckExtendResultToggled(bool checked)
bool eventFilter(QObject *obj, QEvent *event) override
void onCheckResizeToggled(bool checked)
void onFFMpegProgressUpdated(int frameNo)
void onButtonBrowseDirectoryClicked()
void onButtonLockFpsToggled(bool checked)
void onComboProfileIndexChanged(int index)
void onSpinFpsValueChanged(int value)
void onButtonLockRatioToggled(bool checked)
void onSpinScaleWidthValueChanged(int value)
void onButtonBrowseExportClicked()
bool editProfile(RecorderProfile *profile, const RecorderProfile &defaultProfile)
void setPreview(const QString &preview)
void requestPreview(QString arguments)
QIcon loadIcon(const QString &name)
QLatin1String fileExtension(RecorderFormat format)
QList< RecorderProfile > profiles
QList< RecorderProfile > defaultProfiles