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 videoOutputFile(videoOutputFilePath);
105 if (!videoOutputFile.open(QIODevice::WriteOnly)) {
106 qWarning() << "Could not open" << videoOutputFile.fileName() << "for writing! Do you have permission to write to this file?";
107 result = KisImportExportErrorCannotWrite(videoOutputFile.error());
108 } else {
109 videoOutputFile.close();
110 }
111
112 QScopedPointer<KisAnimationVideoSaver> encoder(new KisAnimationVideoSaver(doc, batchMode));
113 result = encoder->convert(doc, savedFilesMask, encoderOptions, batchMode);
114
115 if (!result.isOk()) {
116 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", result.errorMessage()));
117
118 delayReturnSuccess = false; // Delay return to clean up exported frames.
119 }
120 }
121 }
122
123 //File cleanup
124 QDir d(framesDirectory);
125
126 if (encoderOptions.shouldDeleteSequence || delayReturnSuccess == false) {
127 QStringList savedFiles = exporter.savedFiles();
128
129 Q_FOREACH(const QString &f, savedFiles) {
130 if (d.exists(f)) {
131 d.remove(f);
132 }
133 }
134 } else if(encoderOptions.wantsOnlyUniqueFrameSequence) {
135 const QStringList fileNames = exporter.savedFiles();
136 const QStringList uniqueFrameNames = exporter.savedUniqueFiles();
137
138 Q_FOREACH(const QString &f, fileNames) {
139 if (!uniqueFrameNames.contains(f)) {
140 d.remove(f);
141 }
142 }
143 }
144
145 QStringList paletteFiles = d.entryList(QStringList() << "KritaTempPalettegen_*.png", QDir::Files);
146
147 Q_FOREACH(const QString &f, paletteFiles) {
148 d.remove(f);
149 }
151 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");
153 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Rendering error"), i18n("Failed to render animation frames! Output files are incomplete."));
154 }
155
156 return delayReturnSuccess;
157}
158
160{
161 return (mimeType == "video/mp4" || mimeType == "video/x-matroska") && renderMode != KisAnimationRenderingOptions::RENDER_FRAMES_ONLY;
162}
163
164bool KisAnimationRender::hasEvenDimensions(int width, int height)
165{
166 return !((width & 0x1) || (height & 0x1));
167}
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)