Krita Source Code Documentation
Loading...
Searching...
No Matches
KisAnimationVideoSaver Class Reference

#include <KisVideoSaver.h>

+ Inheritance diagram for KisAnimationVideoSaver:

Public Member Functions

KisImportExportErrorCode encode (const QString &framesDirectory, const QString &savedFilesMask, const QStringList &savedFiles, const KisAnimationRenderingOptions &options)
 
KisImageSP image ()
 image
 
 KisAnimationVideoSaver (KisDocument *doc, bool batchMode)
 KisAnimationVideoSaver This is the object that takes an animation document and config and tells ffmpeg to render it via KisFFMpegWrapper or uses KisMediaEncoderWrapper on Android.
 
 ~KisAnimationVideoSaver () override
 

Static Public Member Functions

static KisImportExportErrorCode convert (KisDocument *document, const QString &framesDirectory, const QString &savedFilesMask, const QStringList &savedFiles, const KisAnimationRenderingOptions &options, bool batchMode)
 

Private Attributes

bool m_batchMode
 
KisDocumentm_doc
 
KisImageSP m_image
 

Detailed Description

Definition at line 21 of file KisVideoSaver.h.

Constructor & Destructor Documentation

◆ KisAnimationVideoSaver()

KisAnimationVideoSaver::KisAnimationVideoSaver ( KisDocument * doc,
bool batchMode )

KisAnimationVideoSaver This is the object that takes an animation document and config and tells ffmpeg to render it via KisFFMpegWrapper or uses KisMediaEncoderWrapper on Android.

Parameters
docthe document to use for rendering.
batchModewhether Krita is in batchmode and we can thus not show gui widgets.

Definition at line 41 of file KisVideoSaver.cpp.

42 : m_image(doc->image())
43 , m_doc(doc)
44 , m_batchMode(batchMode)
45{
46}
KisImageSP image

◆ ~KisAnimationVideoSaver()

KisAnimationVideoSaver::~KisAnimationVideoSaver ( )
override

Definition at line 48 of file KisVideoSaver.cpp.

49{
50}

Member Function Documentation

◆ convert()

KisImportExportErrorCode KisAnimationVideoSaver::convert ( KisDocument * document,
const QString & framesDirectory,
const QString & savedFilesMask,
const QStringList & savedFiles,
const KisAnimationRenderingOptions & options,
bool batchMode )
static

Definition at line 287 of file KisVideoSaver.cpp.

293{
294 KisAnimationVideoSaver videoSaver(document, batchMode);
295 KisImportExportErrorCode res = videoSaver.encode(framesDirectory, savedFilesMask, savedFiles, options);
296 return res;
297}

References encode().

◆ encode()

KisImportExportErrorCode KisAnimationVideoSaver::encode ( const QString & framesDirectory,
const QString & savedFilesMask,
const QStringList & savedFiles,
const KisAnimationRenderingOptions & options )

Definition at line 57 of file KisVideoSaver.cpp.

61{
62#ifdef Q_OS_ANDROID
63 Q_UNUSED(savedFilesMask);
64
65 KisMediaEncoderFormat *format = KisMediaEncoderWrapper::getFormatByKey(options.videoFormatKey);
67
68 QVariantMap formatPreferences;
69 if (!options.videoFormatPreferencesJson.isEmpty()) {
70 formatPreferences = QJsonDocument::fromJson(options.videoFormatPreferencesJson.toUtf8()).toVariant().toMap();
71 }
72
74 options.videoFileName,
76 QString(),
77 format,
78 formatPreferences,
79 options.scaleFilter,
80 QSize(options.width, options.height),
81 options.frameRate,
82 options.frameRate,
83 0,
84 0,
85 0,
86 };
87
88 QString inputDir = framesDirectory;
89 if (!inputDir.endsWith(QStringLiteral("/"))) {
90 inputDir.append(QStringLiteral("/"));
91 }
92
93 settings.inputFiles.reserve(savedFiles.size());
94 for (const QString &savedFile : savedFiles) {
95 settings.inputFiles.append(inputDir + savedFile);
96 }
97
98 // MLT can't read from Android content URIs, so we have to copy the file
99 // into temporary storage and get a real path for it.
100 QTemporaryFile audioFile;
101 if (format->supportsAudio() && options.includeAudio) {
103 if (!audioFiles.isEmpty()) {
104 QString audioPath = audioFiles.first().filePath();
105 QString errorMessage;
106 if (KisAndroidUtils::copyFileToTemporary(audioPath, audioFile, &errorMessage)) {
107 settings.audioFile = audioFile.fileName();
108 settings.audioSeekFrame = options.firstFrame;
109 } else {
110 return KisImportExportErrorCode(i18n("Error preparing audio file: %1", errorMessage));
111 }
112 }
113 }
114
115 // This whole batchMode business is either a vestige or was never actually
116 // implemented to completion. It's *supposed* to not show dialogs if Krita
117 // is run in batch mode via the command line, but it's not actually wired up
118 // properly. KisAnimationRender always sets it to false with a TODO to fetch
119 // it correctly and the ffmpeg code here never sets it either, meaning it'll
120 // always be false. For consistency, I've made KisMediaEncoderWrapper behave
121 // the same, even if it's kind of pointless. But if you're working on this
122 // in the future, you should be able to do the same thing for this code path
123 // as you're doing for the ffmpeg one.
124 bool batchMode = false;
125 return KisMediaEncoderWrapper().start(settings, batchMode);
126#else
127 Q_UNUSED(framesDirectory);
128 Q_UNUSED(savedFiles);
129
130 if (!QFileInfo(options.ffmpegPath).exists()) {
131 m_doc->setErrorMessage(i18n("ffmpeg could not be found at %1", options.ffmpegPath));
133 }
134
136
138
139 const int sequenceStart = options.sequenceStart;
140 const KisTimeSpan clipRange = KisTimeSpan::fromTimeToTime(options.firstFrame,
141 options.lastFrame);
142
143 // export dimensions could be off a little bit, so the last force option tweaks the pixels for the export to work
144 const QString exportDimensions =
145 QString("scale=w=")
146 .append(QString::number(options.width))
147 .append(":h=")
148 .append(QString::number(options.height))
149 .append(":flags=")
150 .append(options.scaleFilter);
151 //.append(":force_original_aspect_ratio=decrease"); HOTFIX for even:odd dimension images.
152
153 const QString resultFile = options.resolveAbsoluteVideoFilePath();
154 const QFileInfo resultFileInfo(resultFile);
155 const QDir videoDir(resultFileInfo.absolutePath());
156
157 const QString suffix = resultFileInfo.suffix().toLower();
158 const QString palettePath = videoDir.filePath("KritaTempPalettegen_\%06d.png");
159
160 QStringList additionalOptionsList = options.customFFMpegOptions.split(' ', Qt::SkipEmptyParts);
161
162 QScopedPointer<KisFFMpegWrapper> ffmpegWrapper(new KisFFMpegWrapper(this));
163
164 {
165 QStringList paletteArgs;
166 QStringList simpleFilterArgs;
167 QStringList complexFilterArgs;
168 QStringList args;
169
170 args << "-y" // Auto Confirm...
171 << "-r" << QString::number(options.frameRate) // Frame rate for video...
172 << "-start_number" << QString::number(sequenceStart) << "-start_number_range" << "1"
173 << "-i" << savedFilesMask; // Input frame(s) file mask..
174
175 const int lavfiOptionsIndex = additionalOptionsList.indexOf("-lavfi");
176
177 if ( lavfiOptionsIndex != -1 ) {
178 complexFilterArgs << additionalOptionsList.takeAt(lavfiOptionsIndex + 1);
179
180 additionalOptionsList.removeAt( lavfiOptionsIndex );
181 }
182
183 if ( options.videoMimeType == "image/gif" ) {
184 paletteArgs << "-r" << QString::number(options.frameRate)
185 << "-start_number" << QString::number(sequenceStart) << "-start_number_range" << "1"
186 << "-i" << savedFilesMask;
187
188 const int paletteOptionsIndex = additionalOptionsList.indexOf("-palettegen");
189 QString palettegenString = "palettegen";
190
191 if ( paletteOptionsIndex != -1 ) {
192 palettegenString = additionalOptionsList.takeAt(paletteOptionsIndex + 1);
193
194 additionalOptionsList.removeAt( paletteOptionsIndex );
195 }
196
197 if (m_image->width() != options.width || m_image->height() != options.height) {
198 paletteArgs << "-vf" << (exportDimensions + "," + palettegenString );
199 } else {
200 paletteArgs << "-vf" << palettegenString;
201 }
202
203 paletteArgs << "-y" << palettePath;
204
205 QStringList ffmpegArgs;
206 ffmpegArgs << "-v" << "debug"
207 << paletteArgs;
208
209 KisFFMpegWrapperSettings ffmpegSettings;
210 ffmpegSettings.args = ffmpegArgs;
211 ffmpegSettings.processPath = options.ffmpegPath;
212
213 ffmpegSettings.progressIndeterminate = true;
214 ffmpegSettings.progressMessage = i18nc("Animation export dialog for palette exporting. arg1: file-suffix",
215 "Creating palette for %1 file format.", "[suffix]");
216 ffmpegSettings.logPath = QDir::tempPath() + QDir::separator() + "krita" + QDir::separator() + "ffmpeg.log";
217
218 KisImportExportErrorCode result = ffmpegWrapper->start(ffmpegSettings);
219
220 if (!result.isOk()) {
221 return result;
222 }
223
224 if (lavfiOptionsIndex == -1) {
225 complexFilterArgs << "[0:v][1:v] paletteuse";
226 }
227
228 args << "-i" << palettePath;
229
230 // We need to kill the process so we can reuse it later down the chain. BUG:446320
231 ffmpegWrapper->reset();
232 }
233
235 if (options.includeAudio && audioFiles.count() > 0 && audioFiles.first().exists()) {
236 QFileInfo audioFileInfo = audioFiles.first();
237 const int msecPerFrame = (1000 / animation->framerate());
238 const int msecStart = msecPerFrame * clipRange.start();
239 const int msecDuration = msecPerFrame * clipRange.duration();
240
241 const QTime startTime = QTime::fromMSecsSinceStartOfDay(msecStart);
242 const QTime durationTime = QTime::fromMSecsSinceStartOfDay(msecDuration);
243 const QString ffmpegTimeFormat = QStringLiteral("H:m:s.zzz");
244
245 args << "-ss" << QLocale::c().toString(startTime, ffmpegTimeFormat);
246 args << "-t" << QLocale::c().toString(durationTime, ffmpegTimeFormat);
247 args << "-i" << audioFileInfo.absoluteFilePath();
248 }
249
250 // if we are exporting out at a different image size, we apply scaling filter
251 // export options HAVE to go after input options, so make sure this is after the audio import
252 if (m_image->width() != options.width || m_image->height() != options.height) {
253 simpleFilterArgs << exportDimensions;
254 }
255
256 if ( !complexFilterArgs.isEmpty() ) {
257 args << "-lavfi" << (!simpleFilterArgs.isEmpty() ? simpleFilterArgs.join(",").append("[0:v];"):"") + complexFilterArgs.join(";");
258 } else if ( !simpleFilterArgs.isEmpty() ) {
259 args << "-vf" << simpleFilterArgs.join(",");
260 }
261
262 args << additionalOptionsList;
263
264 dbgFile << "savedFilesMask" << savedFilesMask
265 << "save files offset" << sequenceStart
266 << "start" << QString::number(clipRange.start())
267 << "duration" << clipRange.duration();
268
269
270 KisFFMpegWrapperSettings ffmpegSettings;
271 ffmpegSettings.processPath = options.ffmpegPath;
272 ffmpegSettings.args = args;
273 ffmpegSettings.outputFile = resultFile;
274 ffmpegSettings.totalFrames = clipRange.duration();
275 ffmpegSettings.logPath = QDir::tempPath() + QDir::separator() + "krita" + QDir::separator() + "ffmpeg.log";
276 ffmpegSettings.progressMessage = i18nc("Animation export dialog for tracking ffmpeg progress. arg1: file-suffix, arg2: progress frame number, arg3: totalFrameCount.",
277 "Creating desired %1 file: %2/%3 frames.", "[suffix]", "[progress]", "[framecount]");
278
279 resultOuter = ffmpegWrapper->start(ffmpegSettings);
280 }
281
282
283 return resultOuter;
284#endif
285}
QList< QString > QStringList
QString resolveAbsoluteVideoFilePath(const QString &documentPath) const
void setErrorMessage(const QString &errMsg)
QVector< QFileInfo > getAudioTracks() const
KisImageAnimationInterface * animationInterface() const
qint32 width() const
qint32 height() const
virtual bool supportsAudio() const =0
static KisMediaEncoderFormat * getFormatByKey(const QString &key)
KisImportExportErrorCode start(const KisMediaEncoderWrapperSettings &settings, bool batchMode)
int start() const
int duration() const
static KisTimeSpan fromTimeToTime(int start, int end)
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define dbgFile
Definition kis_debug.h:56
bool copyFileToTemporary(const QString &inputPath, QTemporaryFile &outputFile, QString *outErrorMessage)

References KisImage::animationInterface(), KisFFMpegWrapperSettings::args, KisMediaEncoderWrapperSettings::audioFile, KisMediaEncoderWrapperSettings::audioSeekFrame, KisAndroidUtils::copyFileToTemporary(), KisAnimationRenderingOptions::customFFMpegOptions, dbgFile, KisTimeSpan::duration(), ImportExportCodes::Failure, KisAnimationRenderingOptions::ffmpegPath, KisAnimationRenderingOptions::firstFrame, KisImageAnimationInterface::framerate(), KisAnimationRenderingOptions::frameRate, KisTimeSpan::fromTimeToTime(), KisDocument::getAudioTracks(), KisMediaEncoderWrapper::getFormatByKey(), KisImage::height(), KisAnimationRenderingOptions::height, KisAnimationRenderingOptions::includeAudio, KisMediaEncoderWrapperSettings::inputFiles, ImportExportCodes::InternalError, KisImportExportErrorCode::isOk(), KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE, KisAnimationRenderingOptions::lastFrame, KisFFMpegWrapperSettings::logPath, m_doc, m_image, ImportExportCodes::OK, KisFFMpegWrapperSettings::outputFile, KisFFMpegWrapperSettings::processPath, KisFFMpegWrapperSettings::progressIndeterminate, KisFFMpegWrapperSettings::progressMessage, KisAnimationRenderingOptions::resolveAbsoluteVideoFilePath(), KisAnimationRenderingOptions::scaleFilter, KisAnimationRenderingOptions::sequenceStart, KisDocument::setErrorMessage(), KisTimeSpan::start(), KisMediaEncoderWrapper::start(), KisMediaEncoderFormat::supportsAudio(), KisFFMpegWrapperSettings::totalFrames, KisAnimationRenderingOptions::videoFileName, KisAnimationRenderingOptions::videoMimeType, KisImage::width(), and KisAnimationRenderingOptions::width.

◆ image()

KisImageSP KisAnimationVideoSaver::image ( )

image

Returns
get the image used by this exporter.

Definition at line 52 of file KisVideoSaver.cpp.

53{
54 return m_image;
55}

References m_image.

Member Data Documentation

◆ m_batchMode

bool KisAnimationVideoSaver::m_batchMode
private

Definition at line 55 of file KisVideoSaver.h.

◆ m_doc

KisDocument* KisAnimationVideoSaver::m_doc
private

Definition at line 54 of file KisVideoSaver.h.

◆ m_image

KisImageSP KisAnimationVideoSaver::m_image
private

Definition at line 53 of file KisVideoSaver.h.


The documentation for this class was generated from the following files: