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())
123 fixInternalSnapshotDirectory();
159 title = i18nc(
"Title for label. JPEG Quality level",
"Quality:");
160 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.");
168 title = i18nc(
"Title for label. PNG Compression level",
"Compression:");
169 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.");
177 ui->comboFormat->setCurrentIndex(index);
178 ui->labelQuality->setText(title);
179 ui->spinQuality->setToolTip(hint);
180 QSignalBlocker blocker(
ui->spinQuality);
181 ui->spinQuality->setMinimum(minValue);
182 ui->spinQuality->setMaximum(maxValue);
183 ui->spinQuality->setValue(factor);
184 ui->spinQuality->setSuffix(suffix);
194 QSignalBlocker blocker(
ui->spinRate);
197 title = i18nc(
"Title for label. Video frames per second",
"Video FPS:");
206 title = i18nc(
"Title for label. Capture rate",
"Capture interval:");
216 ui->labelRate->setText(title);
217 ui->spinRate->setDecimals(decimals);
218 ui->spinRate->setMinimum(minValue);
219 ui->spinRate->setMaximum(maxValue);
220 ui->spinRate->setSuffix(suffix);
241 :
canvas->imageView()->document()->documentInfo()->aboutInfo(
"creation-date").remove(QRegularExpression(
"[^0-9]"));
247 i18nc(
"Use original resolution for the frames when recording the canvas",
"Original"),
248 i18nc(
"Use the resolution two times smaller than the original resolution for the frames when recording the canvas",
"Half"),
249 i18nc(
"Use the resolution four times smaller than the original resolution for the frames when recording the canvas",
"Quarter")
253 for (
int index = 0, len = titles.length(); index < len; ++index) {
254 int divider = 1 << index;
255 items += QString(
"%1 (%2x%3)").arg(titles[index])
256 .arg((width / divider) & ~1)
257 .arg((height / divider) & ~1);
259 QSignalBlocker blocker(
ui->comboResolution);
260 const int currentIndex =
ui->comboResolution->currentIndex();
261 ui->comboResolution->clear();
262 ui->comboResolution->addItems(items);
263 ui->comboResolution->setCurrentIndex(currentIndex);
271 QSignalBlocker blocker(
ui->buttonRecordToggle);
272 ui->buttonRecordToggle->setChecked(isRecording);
274 ui->buttonRecordToggle->setText(isRecording ? i18nc(
"Stop recording the canvas",
"Stop")
275 : i18nc(
"Start recording the canvas",
"Record"));
276 ui->buttonRecordToggle->setEnabled(
true);
278 ui->widgetSettings->setEnabled(!isRecording);
300 QString label(
"<font style='letter-spacing:-4px'>");
302 QString inactiveColor;
305 if (threadNr > threads) {
306 activeColor = inactiveColorGray;
307 inactiveColor = inactiveColorGray;
309 activeColor = activeColorRed;
310 inactiveColor = inactiveColorRed;
312 activeColor = activeColorOrange;
313 inactiveColor = inactiveColorOrange;
315 activeColor = activeColorGreen;
316 inactiveColor = inactiveColorGreen;
318 label.append(QString(
"<font%1>▍</font>")
319 .arg(threadNr <= threadsInUse ? activeColor : inactiveColor));
322 label.append(QString(
"</font><font> %1 </font><font%2>●</font>")
323 .arg(i18nc(
"Recording symbol",
"REC"))
324 .arg(
paused ?
"" : activeColorRed));
326 statusBarLabel->setToolTip(
paused ? i18n(
"Recorder is paused") : QString(i18n(
"Active recording with %1 of %2 available threads")).arg(threadsInUse).arg(threads));
345 pal.setColor(QPalette::Text, textColorRed);
346 pal.setColor(QPalette::Button, buttonColorRed);
347 ui->spinThreads->setPalette(pal);
348 ui->sliderThreads->setPalette(pal);
349 toolTipText = QString(
350 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.")
356 pal.setColor(QPalette::Text, textColorOrange);
357 pal.setColor(QPalette::Button, buttonColorOrange);
358 ui->spinThreads->setPalette(pal);
359 ui->sliderThreads->setPalette(pal);
360 toolTipText = QString(
361 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.")
366 toolTipText = i18n(
"Set the number of threads to be used for recording.");
368 ui->spinThreads->setToolTip(toolTipText);
369 ui->sliderThreads->setToolTip(toolTipText);
373 void fixInternalSnapshotDirectory()
385 q->moveFilesFromInternalSnapshotDirectory();
392 : QDockWidget(i18nc(
"Title of the docker",
"Recorder"))
394 , d(new
Private(*exportSettings, this))
396 QWidget* page =
new QWidget(
this);
397 d->
ui->setupUi(page);
403 d->
ui->sliderThreads->setTickPosition(QSlider::TickPosition::TicksBelow);
404 d->
ui->sliderThreads->setMinimum(1);
406 d->
ui->spinThreads->setMinimum(1);
428 connect(
d->
exportAction, SIGNAL(triggered()),
d->
ui->buttonExport, SIGNAL(clicked()));
429 connect(
d->
ui->buttonRecordToggle, SIGNAL(toggled(
bool)),
d->
ui->buttonExport, SLOT(setDisabled(
bool)));
431 d->
ui->buttonExport->setDisabled(
true);
440 connect(
d->
ui->comboFormat, SIGNAL(currentIndexChanged(
int)),
this, SLOT(
onFormatChanged(
int)));
461 connect(scroller, SIGNAL(stateChanged(QScroller::State)),
468 d->
ui->checkBoxRealTimeCaptureMode->setCheckState(Qt::Unchecked);
469 d->
ui->checkBoxRealTimeCaptureMode->setDisabled(
true);
470 d->
ui->checkBoxRealTimeCaptureMode->setToolTip(
471 i18n(
"Your system is not efficient enough for this feature"));
485 setEnabled(canvas !=
nullptr);
500 bool wasToggled =
false;
502 && !
d->
enabledIds.contains(document->linkedResourcesStorageId())) {
511 bool enabled =
d->
enabledIds.value(document->linkedResourcesStorageId(),
false);
536 QSignalBlocker blocker(
d->
ui->buttonRecordToggle);
544 d->
ui->buttonRecordToggle->setChecked(
false);
555 const QString &
id =
d->
canvas->imageView()->document()->linkedResourcesStorageId();
557 bool wasEmpty = !
d->
enabledIds.values().contains(
true);
561 bool isEmpty = !
d->
enabledIds.values().contains(
true);
565 if (isEmpty == wasEmpty) {
571 d->
ui->buttonRecordToggle->setEnabled(
false);
611 exportDialog.
setup();
627 dialog.setCaption(i18n(
"Select a Directory for Recordings"));
628 dialog.setDefaultDir(
d->
ui->editDirectory->text());
629 QString directory = dialog.filename();
630 if (!directory.isEmpty()) {
631 d->
ui->editDirectory->setText(directory);
733 if (!valueWasIncreased)
748 QMessageBox::warning(
this, i18nc(
"@title:window",
"Recorder"),
749 i18n(
"The recorder has been stopped due to failure while writing a frame. Please check free disk space and start the recorder again."));
754 QMessageBox::warning(
this, i18nc(
"@title:window",
"Recorder"),
755 i18n(
"Krita was unable to stop the recorder probably. Please try to restart Krita."));
760 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."));
762 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."));
777void RecorderDockerDock::moveFilesFromInternalSnapshotDirectory()
779 if (!
d->internalMoveInProgress) {
780 const QString &internalPath = RecorderConfig::defaultInternalSnapshotDirectory();
789 qWarning().nospace() <<
"Moving recordings stuck in internal directory '" << internalPath
791 RecorderDockerInternalSnapshotsMover *mover =
794 &RecorderDockerInternalSnapshotsMover::sigMoveFinished,
796 &RecorderDockerDock::slotInternalSnapshotMoveFinished,
797 Qt::QueuedConnection);
798 d->internalMoveInProgress =
true;
799 QThreadPool::globalInstance()->start(mover);
804void RecorderDockerDock::slotInternalSnapshotMoveFinished(
const QString &srcRoot)
806 d->internalMoveInProgress =
false;
809 moveFilesFromInternalSnapshotDirectory();
813RecorderDockerInternalSnapshotsMover::RecorderDockerInternalSnapshotsMover(
const QString &srcRoot,
814 const QString &dstRoot)
820void RecorderDockerInternalSnapshotsMover::run()
822 moveFromInternalSnapshotDirectory(QDir(m_srcRoot), QDir(m_dstRoot));
823 if (!QDir().rmdir(m_srcRoot)) {
824 qWarning().nospace() <<
"Failed to remove root directory '" << m_srcRoot <<
"'";
826 Q_EMIT sigMoveFinished(m_srcRoot);
829void RecorderDockerInternalSnapshotsMover::moveFromInternalSnapshotDirectory(
const QDir &src,
const QDir &dst)
831 for (
const QFileInfo &srcInfo :
src.entryInfoList(FILTERS)) {
832 QString srcName = srcInfo.fileName();
833 QString dstPath = dst.filePath(srcName);
835 if (srcInfo.isDir()) {
837 if (dst.mkpath(dstPath)) {
838 moveFromInternalSnapshotDirectory(QDir(srcInfo.filePath()), QDir(dstPath));
840 qWarning().nospace() <<
"Failed to create directory '" << dstPath <<
"' in '" << dst.path() <<
"'";
845 if (!
src.rmdir(srcName)) {
846 qWarning().nospace() <<
"Failed to remove directory '" << srcName <<
"' in '" <<
src.path() <<
"'";
850 QFile srcFile(srcInfo.filePath());
855 QFile::remove(dstPath);
856 if (!srcFile.rename(dstPath)) {
857 qWarning().nospace() <<
"Error " << srcFile.error() <<
" moving '" << srcFile.fileName() <<
"' to '"
858 << 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