Krita Source Code Documentation
Loading...
Searching...
No Matches
KisVideoSaver.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2016 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "KisVideoSaver.h"
8
9#include <QDebug>
10#include <QFileInfo>
11#include <QFileSystemWatcher>
12#include <QProcess>
13#include <QProgressDialog>
14#include <QEventLoop>
15#include <QTemporaryFile>
16#include <QTemporaryDir>
17#include <QTime>
18
19#include <KisDocument.h>
20#include <kis_image.h>
22#include <kis_time_span.h>
23#include <KoColorSpace.h>
26#include <KoResourcePaths.h>
27#include "kis_config.h"
30
31#include "KisPart.h"
32
34 : m_image(doc->image())
35 , m_doc(doc)
36 , m_batchMode(batchMode)
37{
38}
39
43
48
50{
51 if (!QFileInfo(options.ffmpegPath).exists()) {
52 m_doc->setErrorMessage(i18n("ffmpeg could not be found at %1", options.ffmpegPath));
54 }
55
57
59
60 const int sequenceStart = options.sequenceStart;
61 const KisTimeSpan clipRange = KisTimeSpan::fromTimeToTime(options.firstFrame,
62 options.lastFrame);
63
64 // export dimensions could be off a little bit, so the last force option tweaks the pixels for the export to work
65 const QString exportDimensions =
66 QString("scale=w=")
67 .append(QString::number(options.width))
68 .append(":h=")
69 .append(QString::number(options.height))
70 .append(":flags=")
71 .append(options.scaleFilter);
72 //.append(":force_original_aspect_ratio=decrease"); HOTFIX for even:odd dimension images.
73
74 const QString resultFile = options.resolveAbsoluteVideoFilePath();
75 const QFileInfo resultFileInfo(resultFile);
76 const QDir videoDir(resultFileInfo.absolutePath());
77
78 const QString suffix = resultFileInfo.suffix().toLower();
79 const QString palettePath = videoDir.filePath("KritaTempPalettegen_\%06d.png");
80
81 QStringList additionalOptionsList = options.customFFMpegOptions.split(' ', Qt::SkipEmptyParts);
82
83 QScopedPointer<KisFFMpegWrapper> ffmpegWrapper(new KisFFMpegWrapper(this));
84
85 {
86 QStringList paletteArgs;
87 QStringList simpleFilterArgs;
88 QStringList complexFilterArgs;
89 QStringList args;
90
91 args << "-y" // Auto Confirm...
92 << "-r" << QString::number(options.frameRate) // Frame rate for video...
93 << "-start_number" << QString::number(sequenceStart) << "-start_number_range" << "1"
94 << "-i" << savedFilesMask; // Input frame(s) file mask..
95
96 const int lavfiOptionsIndex = additionalOptionsList.indexOf("-lavfi");
97
98 if ( lavfiOptionsIndex != -1 ) {
99 complexFilterArgs << additionalOptionsList.takeAt(lavfiOptionsIndex + 1);
100
101 additionalOptionsList.removeAt( lavfiOptionsIndex );
102 }
103
104 if ( options.videoMimeType == "image/gif" ) {
105 paletteArgs << "-r" << QString::number(options.frameRate)
106 << "-start_number" << QString::number(sequenceStart) << "-start_number_range" << "1"
107 << "-i" << savedFilesMask;
108
109 const int paletteOptionsIndex = additionalOptionsList.indexOf("-palettegen");
110 QString palettegenString = "palettegen";
111
112 if ( paletteOptionsIndex != -1 ) {
113 palettegenString = additionalOptionsList.takeAt(paletteOptionsIndex + 1);
114
115 additionalOptionsList.removeAt( paletteOptionsIndex );
116 }
117
118 if (m_image->width() != options.width || m_image->height() != options.height) {
119 paletteArgs << "-vf" << (exportDimensions + "," + palettegenString );
120 } else {
121 paletteArgs << "-vf" << palettegenString;
122 }
123
124 paletteArgs << "-y" << palettePath;
125
126 QStringList ffmpegArgs;
127 ffmpegArgs << "-v" << "debug"
128 << paletteArgs;
129
130 KisFFMpegWrapperSettings ffmpegSettings;
131 ffmpegSettings.args = ffmpegArgs;
132 ffmpegSettings.processPath = options.ffmpegPath;
133
134 ffmpegSettings.progressIndeterminate = true;
135 ffmpegSettings.progressMessage = i18nc("Animation export dialog for palette exporting. arg1: file-suffix",
136 "Creating palette for %1 file format.", "[suffix]");
137 ffmpegSettings.logPath = QDir::tempPath() + QDir::separator() + "krita" + QDir::separator() + "ffmpeg.log";
138
139 KisImportExportErrorCode result = ffmpegWrapper->start(ffmpegSettings);
140
141 if (!result.isOk()) {
142 return result;
143 }
144
145 if (lavfiOptionsIndex == -1) {
146 complexFilterArgs << "[0:v][1:v] paletteuse";
147 }
148
149 args << "-i" << palettePath;
150
151 // We need to kill the process so we can reuse it later down the chain. BUG:446320
152 ffmpegWrapper->reset();
153 }
154
156 if (options.includeAudio && audioFiles.count() > 0 && audioFiles.first().exists()) {
157 QFileInfo audioFileInfo = audioFiles.first();
158 const int msecPerFrame = (1000 / animation->framerate());
159 const int msecStart = msecPerFrame * clipRange.start();
160 const int msecDuration = msecPerFrame * clipRange.duration();
161
162 const QTime startTime = QTime::fromMSecsSinceStartOfDay(msecStart);
163 const QTime durationTime = QTime::fromMSecsSinceStartOfDay(msecDuration);
164 const QString ffmpegTimeFormat = QStringLiteral("H:m:s.zzz");
165
166 args << "-ss" << QLocale::c().toString(startTime, ffmpegTimeFormat);
167 args << "-t" << QLocale::c().toString(durationTime, ffmpegTimeFormat);
168 args << "-i" << audioFileInfo.absoluteFilePath();
169 }
170
171 // if we are exporting out at a different image size, we apply scaling filter
172 // export options HAVE to go after input options, so make sure this is after the audio import
173 if (m_image->width() != options.width || m_image->height() != options.height) {
174 simpleFilterArgs << exportDimensions;
175 }
176
177 if ( !complexFilterArgs.isEmpty() ) {
178 args << "-lavfi" << (!simpleFilterArgs.isEmpty() ? simpleFilterArgs.join(",").append("[0:v];"):"") + complexFilterArgs.join(";");
179 } else if ( !simpleFilterArgs.isEmpty() ) {
180 args << "-vf" << simpleFilterArgs.join(",");
181 }
182
183 args << additionalOptionsList;
184
185 dbgFile << "savedFilesMask" << savedFilesMask
186 << "save files offset" << sequenceStart
187 << "start" << QString::number(clipRange.start())
188 << "duration" << clipRange.duration();
189
190
191 KisFFMpegWrapperSettings ffmpegSettings;
192 ffmpegSettings.processPath = options.ffmpegPath;
193 ffmpegSettings.args = args;
194 ffmpegSettings.outputFile = resultFile;
195 ffmpegSettings.totalFrames = clipRange.duration();
196 ffmpegSettings.logPath = QDir::tempPath() + QDir::separator() + "krita" + QDir::separator() + "ffmpeg.log";
197 ffmpegSettings.progressMessage = i18nc("Animation export dialog for tracking ffmpeg progress. arg1: file-suffix, arg2: progress frame number, arg3: totalFrameCount.",
198 "Creating desired %1 file: %2/%3 frames.", "[suffix]", "[progress]", "[framecount]");
199
200 resultOuter = ffmpegWrapper->start(ffmpegSettings);
201 }
202
203
204 return resultOuter;
205}
206
207KisImportExportErrorCode KisAnimationVideoSaver::convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode)
208{
209 KisAnimationVideoSaver videoSaver(document, batchMode);
210 KisImportExportErrorCode res = videoSaver.encode(savedFilesMask, options);
211 return res;
212}
QString resolveAbsoluteVideoFilePath(const QString &documentPath) const
KisImportExportErrorCode encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options)
encode the main encoding function. This in turn calls runFFMpeg, which is a private function inside t...
static KisImportExportErrorCode convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode)
KisAnimationVideoSaver(KisDocument *doc, bool batchMode)
KisAnimationVideoSaver This is the object that takes an animation document and config and tells ffmpe...
~KisAnimationVideoSaver() override
KisImageSP image()
image
void setErrorMessage(const QString &errMsg)
QVector< QFileInfo > getAudioTracks() const
KisImageAnimationInterface * animationInterface() const
qint32 width() const
qint32 height() const
int start() const
int duration() const
static KisTimeSpan fromTimeToTime(int start, int end)
#define dbgFile
Definition kis_debug.h:53