12#include "ui_recorderdocker.h"
18#include <klocalizedstring.h>
36#include <QRegularExpression>
46const QString keyActionRecordToggle =
"recorder_record_toggle";
47const QString keyActionExport =
"recorder_export";
49const QString activeColorGreen(
" color='#5cab25'");
50const QString inactiveColorGreen(
" color='#b4e196'");
51const QString activeColorOrange(
" color='#ca8f14'");
52const QString inactiveColorOrange(
" color='#ffe5af'");
53const QString activeColorRed(
" color='#da4453'");
54const QString inactiveColorRed(
" color='#f2c4c9'");
55const QString inactiveColorGray(
" color='#3e3e3e'");
57const QColor textColorOrange(0xff, 0xe5, 0xaf);
58const QColor buttonColorOrange(0xca, 0x8f, 0x14);
59const QColor textColorRed(0xf2, 0xc4, 0xc9);
60const QColor buttonColorRed(0xda, 0x44, 0x53);
69 QScopedPointer<Ui::RecorderDocker>
ui;
91 bool internalMoveInProgress{
false};
103 ,
ui(new
Ui::RecorderDocker())
122 QSize imageSize =
canvas->image()->size();
125 quint64 totalSize = (quint64)imageSize.width() * (quint64)imageSize.height() * 4;
127 return totalSize <= 2147483647;
135 fixInternalSnapshotDirectory();
171 title = i18nc(
"Title for label. JPEG Quality level",
"Quality:");
172 hint = i18nc(
"@tooltip",
"Greater value will produce a larger file and a better quality. Doesn't affect CPU consumption.\nValues lower than 50 are not recommended due to high artifacts.");
180 title = i18nc(
"Title for label. PNG Compression level",
"Compression:");
181 hint = i18nc(
"@tooltip",
"Greater value will produce a smaller file but will require more from your CPU. Doesn't affect quality.\nCompression set to 0 is not recommended due to high disk space consumption.\nValues above 3 are not recommended due to high performance impact.");
189 ui->comboFormat->setCurrentIndex(index);
190 ui->labelQuality->setText(title);
191 ui->spinQuality->setToolTip(hint);
192 QSignalBlocker blocker(
ui->spinQuality);
193 ui->spinQuality->setMinimum(minValue);
194 ui->spinQuality->setMaximum(maxValue);
195 ui->spinQuality->setValue(factor);
196 ui->spinQuality->setSuffix(suffix);
206 QSignalBlocker blocker(
ui->spinRate);
209 title = i18nc(
"Title for label. Video frames per second",
"Video FPS:");
218 title = i18nc(
"Title for label. Capture rate",
"Capture interval:");
228 ui->labelRate->setText(title);
229 ui->spinRate->setDecimals(decimals);
230 ui->spinRate->setMinimum(minValue);
231 ui->spinRate->setMaximum(maxValue);
232 ui->spinRate->setSuffix(suffix);
253 :
canvas->imageView()->document()->documentInfo()->aboutInfo(
"creation-date").remove(QRegularExpression(
"[^0-9]"));
259 i18nc(
"Use original resolution for the frames when recording the canvas",
"Original"),
260 i18nc(
"Use the resolution two times smaller than the original resolution for the frames when recording the canvas",
"Half"),
261 i18nc(
"Use the resolution four times smaller than the original resolution for the frames when recording the canvas",
"Quarter")
265 for (
int index = 0, len = titles.length(); index < len; ++index) {
266 int divider = 1 << index;
267 items += QString(
"%1 (%2x%3)").arg(titles[index])
268 .arg((width / divider) & ~1)
269 .arg((height / divider) & ~1);
271 QSignalBlocker blocker(
ui->comboResolution);
272 const int currentIndex =
ui->comboResolution->currentIndex();
273 ui->comboResolution->clear();
274 ui->comboResolution->addItems(items);
275 ui->comboResolution->setCurrentIndex(currentIndex);
283 QSignalBlocker blocker(
ui->buttonRecordToggle);
284 ui->buttonRecordToggle->setChecked(isRecording);
286 ui->buttonRecordToggle->setText(isRecording ? i18nc(
"Stop recording the canvas",
"Stop")
287 : i18nc(
"Start recording the canvas",
"Record"));
290 ui->buttonRecordToggle->setEnabled(
true);
291 ui->buttonRecordToggle->setToolTip(
"");
293 ui->buttonRecordToggle->setEnabled(
false);
294 ui->buttonRecordToggle->setToolTip(i18n(
"Image too large to be recorded"));
296 ui->widgetSettings->setEnabled(!isRecording);
318 QString label(
"<font style='letter-spacing:-4px'>");
320 QString inactiveColor;
323 if (threadNr > threads) {
324 activeColor = inactiveColorGray;
325 inactiveColor = inactiveColorGray;
327 activeColor = activeColorRed;
328 inactiveColor = inactiveColorRed;
330 activeColor = activeColorOrange;
331 inactiveColor = inactiveColorOrange;
333 activeColor = activeColorGreen;
334 inactiveColor = inactiveColorGreen;
336 label.append(QString(
"<font%1>▍</font>")
337 .arg(threadNr <= threadsInUse ? activeColor : inactiveColor));
340 label.append(QString(
"</font><font> %1 </font><font%2>●</font>")
341 .arg(i18nc(
"Recording symbol",
"REC"))
342 .arg(
paused ?
"" : activeColorRed));
344 statusBarLabel->setToolTip(
paused ? i18n(
"Recorder is paused") : QString(i18n(
"Active recording with %1 of %2 available threads")).arg(threadsInUse).arg(threads));
363 pal.setColor(QPalette::Text, textColorRed);
364 pal.setColor(QPalette::Button, buttonColorRed);
365 ui->spinThreads->setPalette(pal);
366 ui->sliderThreads->setPalette(pal);
367 toolTipText = QString(
368 i18n(
"Set the number of recording threads.\nThe number of threads exceeds the ideal max number of your hardware setup.\nPlease be aware, that a number greater than %1 probably won't give you any performance boost.")
374 pal.setColor(QPalette::Text, textColorOrange);
375 pal.setColor(QPalette::Button, buttonColorOrange);
376 ui->spinThreads->setPalette(pal);
377 ui->sliderThreads->setPalette(pal);
378 toolTipText = QString(
379 i18n(
"Set the number of recording threads.\nAccording to your hardware setup you should record with no more than %1 threads.\nYou can play around with one or two more threads, but keep an eye on your overall system performance.")
384 toolTipText = i18n(
"Set the number of threads to be used for recording.");
386 ui->spinThreads->setToolTip(toolTipText);
387 ui->sliderThreads->setToolTip(toolTipText);
391 void fixInternalSnapshotDirectory()
403 q->moveFilesFromInternalSnapshotDirectory();
410 : QDockWidget(i18nc(
"Title of the docker",
"Recorder"))
412 , d(new
Private(*exportSettings, this))
414 QWidget* page =
new QWidget(
this);
415 d->
ui->setupUi(page);
421 d->
ui->sliderThreads->setTickPosition(QSlider::TickPosition::TicksBelow);
422 d->
ui->sliderThreads->setMinimum(1);
424 d->
ui->spinThreads->setMinimum(1);
446 connect(
d->
exportAction, SIGNAL(triggered()),
d->
ui->buttonExport, SIGNAL(clicked()));
447 connect(
d->
ui->buttonRecordToggle, SIGNAL(toggled(
bool)),
d->
ui->buttonExport, SLOT(setDisabled(
bool)));
449 d->
ui->buttonExport->setDisabled(
true);
458 connect(
d->
ui->comboFormat, SIGNAL(currentIndexChanged(
int)),
this, SLOT(
onFormatChanged(
int)));
479 connect(scroller, SIGNAL(stateChanged(QScroller::State)),
486 d->
ui->checkBoxRealTimeCaptureMode->setCheckState(Qt::Unchecked);
487 d->
ui->checkBoxRealTimeCaptureMode->setDisabled(
true);
488 d->
ui->checkBoxRealTimeCaptureMode->setToolTip(
489 i18n(
"Your system is not efficient enough for this feature"));
503 setEnabled(canvas !=
nullptr);
518 bool wasToggled =
false;
529 bool enabled =
d->
enabledIds.value(document->linkedResourcesStorageId(),
false);
554 QSignalBlocker blocker(
d->
ui->buttonRecordToggle);
562 d->
ui->buttonRecordToggle->setChecked(
false);
573 const QString &
id =
d->
canvas->imageView()->document()->linkedResourcesStorageId();
575 bool wasEmpty = !
d->
enabledIds.values().contains(
true);
579 bool isEmpty = !
d->
enabledIds.values().contains(
true);
583 if (isEmpty == wasEmpty) {
589 d->
ui->buttonRecordToggle->setEnabled(
false);
631 exportDialog.
setup();
647 dialog.setCaption(i18n(
"Select a Directory for Recordings"));
648 dialog.setDefaultDir(
d->
ui->editDirectory->text());
649 QString directory = dialog.filename();
650 if (!directory.isEmpty()) {
651 d->
ui->editDirectory->setText(directory);
753 if (!valueWasIncreased)
768 QMessageBox::warning(
this, i18nc(
"@title:window",
"Recorder"),
769 i18n(
"The recorder has been stopped due to failure while writing a frame. Please check free disk space and start the recorder again."));
774 QMessageBox::warning(
this, i18nc(
"@title:window",
"Recorder"),
775 i18n(
"Krita was unable to stop the recorder probably. Please try to restart Krita."));
780 d->
showWarning(i18n(
"Low performance warning. The recorder is not able to write all the frames in time during Real Time Capture mode.\nTry to reduce the frame rate for the ffmpeg export or reduce the scaling filtering in the canvas acceleration settings."));
782 d->
showWarning(i18n(
"Low performance warning. The recorder is not able to write all the frames in time.\nTry to increase the capture interval or reduce the scaling filtering in the canvas acceleration settings."));
797void RecorderDockerDock::moveFilesFromInternalSnapshotDirectory()
799 if (!
d->internalMoveInProgress) {
800 const QString &internalPath = RecorderConfig::defaultInternalSnapshotDirectory();
809 qWarning().nospace() <<
"Moving recordings stuck in internal directory '" << internalPath
811 RecorderDockerInternalSnapshotsMover *mover =
814 &RecorderDockerInternalSnapshotsMover::sigMoveFinished,
816 &RecorderDockerDock::slotInternalSnapshotMoveFinished,
817 Qt::QueuedConnection);
818 d->internalMoveInProgress =
true;
819 QThreadPool::globalInstance()->start(mover);
824void RecorderDockerDock::slotInternalSnapshotMoveFinished(
const QString &srcRoot)
826 d->internalMoveInProgress =
false;
829 moveFilesFromInternalSnapshotDirectory();
833RecorderDockerInternalSnapshotsMover::RecorderDockerInternalSnapshotsMover(
const QString &srcRoot,
834 const QString &dstRoot)
840void RecorderDockerInternalSnapshotsMover::run()
842 moveFromInternalSnapshotDirectory(QDir(m_srcRoot), QDir(m_dstRoot));
843 if (!QDir().rmdir(m_srcRoot)) {
844 qWarning().nospace() <<
"Failed to remove root directory '" << m_srcRoot <<
"'";
846 Q_EMIT sigMoveFinished(m_srcRoot);
849void RecorderDockerInternalSnapshotsMover::moveFromInternalSnapshotDirectory(
const QDir &src,
const QDir &dst)
851 for (
const QFileInfo &srcInfo :
src.entryInfoList(FILTERS)) {
852 QString srcName = srcInfo.fileName();
853 QString dstPath = dst.filePath(srcName);
855 if (srcInfo.isDir()) {
857 if (dst.mkpath(dstPath)) {
858 moveFromInternalSnapshotDirectory(QDir(srcInfo.filePath()), QDir(dstPath));
860 qWarning().nospace() <<
"Failed to create directory '" << dstPath <<
"' in '" << dst.path() <<
"'";
865 if (!
src.rmdir(srcName)) {
866 qWarning().nospace() <<
"Failed to remove directory '" << srcName <<
"' in '" <<
src.path() <<
"'";
870 QFile srcFile(srcInfo.filePath());
875 QFile::remove(dstPath);
876 if (!srcFile.rename(dstPath)) {
877 qWarning().nospace() <<
"Error " << srcFile.error() <<
" moving '" << srcFile.fileName() <<
"' to '"
878 << dstPath <<
"': " << srcFile.errorString();
float value(const T *src, size_t ch)
QAction * makeQAction(const QString &name, QObject *parent=0)
static KisActionRegistry * instance()
A container for a set of QAction objects.
Q_INVOKABLE QAction * addAction(const QString &name, QAction *action)
KisViewManager * viewManager
static KisPart * instance()
void removeExtraWidget(QWidget *widget)
void addExtraWidget(QWidget *widget)
virtual KisKActionCollection * actionCollection() const
double captureInterval() const
QString snapshotDirectory() const
void setFormat(RecorderFormat value)
void setRealTimeCaptureMode(bool value)
bool recordIsolateLayerMode() const
RecorderFormat format() const
void setRecordAutomatically(bool value)
void setResolution(int value)
void setCaptureInterval(double value)
void setSnapshotDirectory(const QString &value)
void setRecordIsolateLayerMode(bool value)
void setCompression(int value)
bool recordAutomatically() const
void setQuality(int value)
bool realTimeCaptureMode() const
void setThreads(int value)
bool recordIsolateLayerMode
QPointer< KisCanvas2 > canvas
void showWarning(const QString &hint)
QString snapshotDirectory
void loadRelevantExportSettings()
QPalette threadsSpinPalette
RecorderWriterManager writer
QMap< QString, bool > enabledIds
Private(const RecorderExportSettings &es, RecorderDockerDock *q_ptr)
void updateUiForRealTimeMode()
QScopedPointer< Ui::RecorderDocker > ui
void updateWriterSettings()
void updateRecIndicator()
void updateRecordStatus(bool isRecording)
QAction * recordToggleAction
QLabel * statusBarWarningLabel
RecorderDockerDock *const q
void updateComboResolution(quint32 width, quint32 height)
QPalette threadsSliderPalette
void slotScrollerStateChanged(QScroller::State state)
RecorderExportSettings *const exportSettings
void onManageRecordingsButtonClicked()
void onCaptureIntervalChanged(double interval)
void setCanvas(KoCanvasBase *canvas) override
void onThreadsChanged(int threads)
void onVideoFPSChanged(double interval)
void slotSelectSnapshotDirectory()
void onQualityChanged(int value)
void onWriterFrameWriteFailed()
void onRecordIsolateLayerModeToggled(bool checked)
void onUpdateRecIndicator()
void onRealTimeCaptureModeToggled(bool checked)
void unsetCanvas() override
void onResolutionChanged(int resolution)
bool onRecordButtonToggled(bool checked)
void onAutoRecordToggled(bool checked)
void onRecorderStopWarning()
void onActiveRecording(bool valueWasIncreased)
void onMainWindowIsBeingCreated(KisMainWindow *window)
void onLowPerformanceWarning()
void onExportButtonClicked()
void onFormatChanged(int format)
void execFor(const QString &snapshotsDirectory)
void start(bool toggleEnabled=true)
void setCanvas(QPointer< KisCanvas2 > canvas)
bool stop(bool toggleEnabled=true)
void setEnabled(bool enabled)
void setup(const RecorderWriterSettings &settings)
ThreadCounter recorderThreads
unsigned int getUsed() const
QIcon loadIcon(const QString &name)
const unsigned int IdealRecordThreadCount
const unsigned int MaxThreadCount
const unsigned int MaxRecordThreadCount
bool realTimeCaptureModeWasSet