Krita Source Code Documentation
Loading...
Searching...
No Matches
KisAnimationRender.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2020 Eoin O 'Neill <eoinoneill1991@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <QFile>
10#include <QFileInfo>
11#include <QDir>
12#include <QMessageBox>
13#include <QApplication>
14
15#include "KisDocument.h"
16#include "KisViewManager.h"
18#include "KisMimeDatabase.h"
20#include "kis_time_span.h"
21#include "KisMainWindow.h"
22
24
25#include "KisVideoSaver.h"
26
28 const QString frameMimeType = encoderOptions.frameMimeType;
29 const QString framesDirectory = encoderOptions.resolveAbsoluteFramesDirectory();
30 const QString extension = KisMimeDatabase::suffixesForMimeType(frameMimeType).first();
31 const QString baseFileName = QString("%1/%2.%3").arg(framesDirectory, encoderOptions.basename, extension);
32
33 if (mustHaveEvenDimensions(encoderOptions.videoMimeType, encoderOptions.renderMode())) {
34 if (hasEvenDimensions(encoderOptions.width, encoderOptions.height) != true) {
35 encoderOptions.width = encoderOptions.width + (encoderOptions.width & 0x1);
36 encoderOptions.height = encoderOptions.height + (encoderOptions.height & 0x1);
37 }
38 }
39
40 const QSize scaledSize = doc->image()->bounds().size().scaled(encoderOptions.width, encoderOptions.height, Qt::IgnoreAspectRatio);
41
42 if (mustHaveEvenDimensions(encoderOptions.videoMimeType, encoderOptions.renderMode())) {
43 if (hasEvenDimensions(scaledSize.width(), scaledSize.height()) != true) {
44 QString type = encoderOptions.videoMimeType == "video/mp4" ? "Mpeg4 (.mp4) " : "Matroska (.mkv) ";
45
46 qWarning() << type <<"requires width and height to be even, resize and try again!";
47 doc->setErrorMessage(i18n("%1 requires width and height to be even numbers. Please resize or crop the image before exporting.", type));
48 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage()));
49
50 return false;
51 }
52 }
53
54 const bool batchMode = false; // TODO: fetch correctly!
57 encoderOptions.lastFrame),
58 baseFileName,
59 encoderOptions.sequenceStart,
60 encoderOptions.wantsOnlyUniqueFrameSequence && !encoderOptions.shouldEncodeVideo,
61 encoderOptions.frameExportConfig);
62 exporter.setBatchMode(batchMode);
63
65 exporter.regenerateRange(viewManager->mainWindow()->viewManager());
66
67 bool delayReturnSuccess = (result == KisAsyncAnimationFramesSaveDialog::RenderComplete);
68
69 // the folder could have been read-only or something else could happen
70 if ((encoderOptions.shouldEncodeVideo || encoderOptions.wantsOnlyUniqueFrameSequence) &&
72
73 const QString savedFilesMask = exporter.savedFilesMask();
74
75 if (encoderOptions.shouldEncodeVideo) {
76 const QString videoOutputFilePath = encoderOptions.resolveAbsoluteVideoFilePath();
77 KIS_SAFE_ASSERT_RECOVER_NOOP(QFileInfo(videoOutputFilePath).isAbsolute());
78
79 const QFileInfo videoOutputFile(videoOutputFilePath);
80 QDir outputDir(videoOutputFile.absolutePath());
81
82 if (!outputDir.exists()) {
83 outputDir.mkpath(videoOutputFile.absolutePath());
84 }
85 KIS_SAFE_ASSERT_RECOVER_NOOP(outputDir.exists());
86
87 // If file exists at output path, prompt user for overwrite..
88 bool videoFileWriteAllowed = true;
89 if (videoOutputFile.exists()) {
90 QMessageBox videoOverwritePrompt;
91
92 videoOverwritePrompt.setText(i18n("Overwrite existing video?"));
93 videoOverwritePrompt.setInformativeText(i18n("A file already exists at the path where you want to render your video [%1]... \n\
94 Are you sure you want to overwrite the existing file?", videoOutputFilePath));
95 videoOverwritePrompt.setStandardButtons(QMessageBox::Ok | QMessageBox::Abort);
96
97 videoFileWriteAllowed = videoOverwritePrompt.exec() == QMessageBox::Ok ? true : false;
98 }
99
100 // Write the video..
101 if (videoFileWriteAllowed) {
103
104 QFile videoFile(videoOutputFilePath);
105 if (!videoFile.open(QIODevice::WriteOnly)) {
106 qWarning() << "Could not open" << videoFile.fileName() << "for writing! Do you have permission to write to this file?";
107 exportResult = KisImportExportErrorCannotWrite(videoFile.error());
108 } else {
109 videoFile.close();
110 }
111
112 if (exportResult.isOk()) {
113 QScopedPointer<KisAnimationVideoSaver> encoder(new KisAnimationVideoSaver(doc, batchMode));
114 exportResult = encoder->convert(doc, savedFilesMask, encoderOptions, batchMode);
115 }
116
117 if (!exportResult.isOk()) {
118 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", exportResult.errorMessage()));
119
120 delayReturnSuccess = false; // Delay return to clean up exported frames.
121 }
122 }
123 }
124
125 //File cleanup
126 QDir d(framesDirectory);
127
128 if (encoderOptions.shouldDeleteSequence || delayReturnSuccess == false) {
129 QStringList savedFiles = exporter.savedFiles();
130
131 Q_FOREACH(const QString &f, savedFiles) {
132 if (d.exists(f)) {
133 d.remove(f);
134 }
135 }
136 } else if(encoderOptions.wantsOnlyUniqueFrameSequence) {
137 const QStringList fileNames = exporter.savedFiles();
138 const QStringList uniqueFrameNames = exporter.savedUniqueFiles();
139
140 Q_FOREACH(const QString &f, fileNames) {
141 if (!uniqueFrameNames.contains(f)) {
142 d.remove(f);
143 }
144 }
145 }
146
147 QStringList paletteFiles = d.entryList(QStringList() << "KritaTempPalettegen_*.png", QDir::Files);
148
149 Q_FOREACH(const QString &f, paletteFiles) {
150 d.remove(f);
151 }
153 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Rendering error"), "Animation frame rendering has timed out. Output files are incomplete.\nTry to increase \"Frame Rendering Timeout\" or reduce \"Frame Rendering Clones Limit\" in Krita settings");
155 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Rendering error"), i18n("Failed to render animation frames! Output files are incomplete."));
156 }
157
158 return delayReturnSuccess;
159}
160
162{
163 return (mimeType == "video/mp4" || mimeType == "video/x-matroska") && renderMode != KisAnimationRenderingOptions::RENDER_FRAMES_ONLY;
164}
165
166bool KisAnimationRender::hasEvenDimensions(int width, int height)
167{
168 return !((width & 0x1) || (height & 0x1));
169}
QList< QString > QStringList
KisPropertiesConfigurationSP frameExportConfig
QString resolveAbsoluteVideoFilePath(const QString &documentPath) const
QString resolveAbsoluteFramesDirectory(const QString &documentPath) const
Result regenerateRange(KisViewManager *viewManager) override
start generation of frames and (if not in batch mode) show the dialog
void setBatchMode(bool value)
setting batch mode to true will prevent any dialogs or message boxes from showing on screen....
KisImageSP image
void setErrorMessage(const QString &errMsg)
QString errorMessage() const
QRect bounds() const override
KisViewManager * viewManager
static QStringList suffixesForMimeType(const QString &mimeType)
static KisTimeSpan fromTimeToTime(int start, int end)
KisMainWindow * mainWindow() const
Encoder * encoder(Imf::OutputFile &file, const ExrPaintLayerSaveInfo &info, int width)
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
bool mustHaveEvenDimensions(const QString &mimeType, KisAnimationRenderingOptions::RenderMode renderMode)
bool hasEvenDimensions(int width, int height)
KRITAUI_EXPORT bool render(KisDocument *doc, KisViewManager *viewManager, KisAnimationRenderingOptions encoderOptions)