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);
613 exportDialog.
setup();
629 dialog.setCaption(i18n(
"Select a Directory for Recordings"));
630 dialog.setDefaultDir(
d->
ui->editDirectory->text());
631 QString directory = dialog.filename();
632 if (!directory.isEmpty()) {
633 d->
ui->editDirectory->setText(directory);
735 if (!valueWasIncreased)
750 QMessageBox::warning(
this, i18nc(
"@title:window",
"Recorder"),
751 i18n(
"The recorder has been stopped due to failure while writing a frame. Please check free disk space and start the recorder again."));
756 QMessageBox::warning(
this, i18nc(
"@title:window",
"Recorder"),
757 i18n(
"Krita was unable to stop the recorder probably. Please try to restart Krita."));
762 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."));
764 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."));
779void RecorderDockerDock::moveFilesFromInternalSnapshotDirectory()
781 if (!
d->internalMoveInProgress) {
782 const QString &internalPath = RecorderConfig::defaultInternalSnapshotDirectory();
791 qWarning().nospace() <<
"Moving recordings stuck in internal directory '" << internalPath
793 RecorderDockerInternalSnapshotsMover *mover =
796 &RecorderDockerInternalSnapshotsMover::sigMoveFinished,
798 &RecorderDockerDock::slotInternalSnapshotMoveFinished,
799 Qt::QueuedConnection);
800 d->internalMoveInProgress =
true;
801 QThreadPool::globalInstance()->start(mover);
806void RecorderDockerDock::slotInternalSnapshotMoveFinished(
const QString &srcRoot)
808 d->internalMoveInProgress =
false;
811 moveFilesFromInternalSnapshotDirectory();
815RecorderDockerInternalSnapshotsMover::RecorderDockerInternalSnapshotsMover(
const QString &srcRoot,
816 const QString &dstRoot)
822void RecorderDockerInternalSnapshotsMover::run()
824 moveFromInternalSnapshotDirectory(QDir(m_srcRoot), QDir(m_dstRoot));
825 if (!QDir().rmdir(m_srcRoot)) {
826 qWarning().nospace() <<
"Failed to remove root directory '" << m_srcRoot <<
"'";
828 Q_EMIT sigMoveFinished(m_srcRoot);
831void RecorderDockerInternalSnapshotsMover::moveFromInternalSnapshotDirectory(
const QDir &src,
const QDir &dst)
833 for (
const QFileInfo &srcInfo :
src.entryInfoList(FILTERS)) {
834 QString srcName = srcInfo.fileName();
835 QString dstPath = dst.filePath(srcName);
837 if (srcInfo.isDir()) {
839 if (dst.mkpath(dstPath)) {
840 moveFromInternalSnapshotDirectory(QDir(srcInfo.filePath()), QDir(dstPath));
842 qWarning().nospace() <<
"Failed to create directory '" << dstPath <<
"' in '" << dst.path() <<
"'";
847 if (!
src.rmdir(srcName)) {
848 qWarning().nospace() <<
"Failed to remove directory '" << srcName <<
"' in '" <<
src.path() <<
"'";
852 QFile srcFile(srcInfo.filePath());
857 QFile::remove(dstPath);
858 if (!srcFile.rename(dstPath)) {
859 qWarning().nospace() <<
"Error " << srcFile.error() <<
" moving '" << srcFile.fileName() <<
"' to '"
860 << 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