17 #include "movieexporter.h"
25 #include <QApplication>
26 #include <QStandardPaths>
32 #include "layercamera.h"
33 #include "layersound.h"
34 #include "soundclip.h"
37 MovieExporter::MovieExporter()
41 MovieExporter::~MovieExporter()
73 std::function<
void(
float,
float)> majorProgress,
74 std::function<
void(
float)> minorProgress,
75 std::function<
void(
QString)> progressMessage)
77 majorProgress(0.f, 0.03f);
79 progressMessage(
QObject::tr(
"Checking environment..."));
83 QString ffmpegPath = ffmpegLocation();
84 qDebug() << ffmpegPath;
88 qCritical() <<
"Please place ffmpeg.exe in " << ffmpegPath <<
" directory";
90 qCritical() <<
"Please place ffmpeg in " << ffmpegPath <<
" directory";
92 return Status::ERROR_FFMPEG_NOT_FOUND;
95 STATUS_CHECK(checkInputParameters(desc))
98 qDebug() <<
"OutFile: " << mDesc.strFileName;
103 Q_ASSERT(
false &&
"Cannot create temp folder.");
107 mTempWorkDir = mTempDir.
path();
112 majorProgress(0.03f, 1.f);
115 STATUS_CHECK(
generateGif(obj, ffmpegPath, desc.strFileName, minorProgress))
119 majorProgress(0.03f, 0.25f);
120 progressMessage(
QObject::tr(
"Assembling audio..."));
124 majorProgress(0.25f, 1.f);
125 progressMessage(
QObject::tr(
"Generating movie..."));
126 STATUS_CHECK(
generateMovie(obj, ffmpegPath, desc.strFileName, minorProgress))
129 majorProgress(1.f, 1.f);
132 clock_t t2 = clock() - t1;
133 qDebug(
"MOVIE = %.1f sec", static_cast<double>(t2 / CLOCKS_PER_SEC));
157 std::function<
void(
float)> progress)
160 const int startFrame = mDesc.startFrame;
161 const int endFrame = mDesc.endFrame;
162 const int fps = mDesc.fps;
164 Q_ASSERT(startFrame >= 0);
165 Q_ASSERT(endFrame >= startFrame);
167 QDir dir(mTempWorkDir);
171 qDebug() <<
"TempAudio=" << tempAudioPath;
173 std::vector< SoundClip* > allSoundClips;
175 std::vector< LayerSound* > allSoundLayers = obj->getLayersByType<
LayerSound>();
178 layer->foreachKeyFrame([&allSoundClips](
KeyFrame* key)
180 allSoundClips.push_back(static_cast<SoundClip*>(key));
184 if (allSoundClips.empty())
return Status::SAFE;
188 QString filterComplex, amergeInput, panChannelLayout;
191 int wholeLen = qCeil((endFrame - startFrame) * 44100.0 / fps);
192 for (
auto clip : allSoundClips)
196 return Status::CANCELED;
200 args <<
"-i" << clip->fileName();
204 filterComplex +=
QString(
"[%1:a:0] aformat=sample_fmts=fltp:sample_rates=44100:channel_layouts=mono,volume=1,adelay=%2S|%2S,apad=whole_len=%3[ad%1];")
205 .
arg(clipCount).
arg(qRound(44100.0 * (clip->pos() - 1) / fps)).arg(wholeLen);
206 amergeInput +=
QString(
"[ad%1]").
arg(clipCount);
207 panChannelLayout +=
QString(
"c%1+").
arg(clipCount);
212 panChannelLayout.
chop(1);
215 args <<
"-filter_complex" <<
QString(
"%1%2 amerge=inputs=%3, pan=mono|c0=%4 [out]")
216 .
arg(filterComplex).
arg(amergeInput).
arg(clipCount).
arg(panChannelLayout);
219 args <<
"-ar" <<
"44100" <<
"-acodec" <<
"pcm_s16le" <<
"-ac" <<
"2" <<
"-map" <<
"[out]" <<
"-y";
221 args <<
"-ss" <<
QString::number((startFrame - 1) / static_cast<double>(fps));
224 args << tempAudioPath;
226 STATUS_CHECK(
MovieExporter::executeFFmpeg(ffmpegPath, args, [&progress,
this] (
int frame) { progress(frame / static_cast<float>(mDesc.endFrame - mDesc.startFrame));
return !mCanceled; }))
227 qDebug() <<
"audio file: " + tempAudioPath;
252 std::function<
void(
float)> progress)
256 return Status::CANCELED;
261 int frameStart = mDesc.startFrame;
262 int frameEnd = mDesc.endFrame;
263 const QSize exportSize = mDesc.exportSize;
264 bool transparency = mDesc.alpha;
265 QString strCameraName = mDesc.strCameraName;
266 bool loop = mDesc.loop;
268 auto cameraLayer =
static_cast<LayerCamera*
>(obj->findLayerByName(strCameraName, Layer::CAMERA));
269 if (cameraLayer ==
nullptr)
271 cameraLayer = obj->getLayersByType<
LayerCamera >().front();
273 int currentFrame = frameStart;
286 imageToExportBase.
fill(bgColor);
288 QSize camSize = cameraLayer->getViewSize();
300 int frameWindow =
static_cast<int>(1e9 / (camSize.
width() * camSize.
height() * 4.0));
307 QStringList args = {
"-f",
"rawvideo",
"-pixel_format",
"bgra"};
317 args <<
"-i" << tempAudioPath;
322 args <<
"-plays" << (loop ?
"0" :
"1");
327 args <<
"-pix_fmt" <<
"yuv420p";
332 args <<
"-q:v" <<
"5";
336 args << strOutputFile;
342 if(framesProcessed < 0)
347 if(currentFrame > frameEnd)
353 if((currentFrame - frameStart <= framesProcessed + frameWindow || failCounter > 10) && currentFrame <= frameEnd)
355 QImage imageToExport = imageToExportBase.
copy();
358 QTransform view = cameraLayer->getViewAtFrame(currentFrame);
362 obj->paintImage(painter, currentFrame,
false,
true);
367 int bytesWritten = ffmpeg.
write(reinterpret_cast<const char*>(imageToExport.
constBits()), imageToExport.
byteCount());
368 Q_ASSERT(bytesWritten == imageToExport.
byteCount());
397 std::function<
void(
float)> progress)
402 return Status::CANCELED;
407 int frameStart = mDesc.startFrame;
408 int frameEnd = mDesc.endFrame;
409 const QSize exportSize = mDesc.exportSize;
410 bool transparency =
false;
411 QString strCameraName = mDesc.strCameraName;
412 bool loop = mDesc.loop;
415 auto cameraLayer =
static_cast<LayerCamera*
>(obj->findLayerByName(strCameraName, Layer::CAMERA));
416 if (cameraLayer ==
nullptr)
418 cameraLayer = obj->getLayersByType<
LayerCamera >().front();
420 int currentFrame = frameStart;
433 imageToExportBase.
fill(bgColor);
435 QSize camSize = cameraLayer->getViewSize();
441 QStringList args = {
"-f",
"rawvideo",
"-pixel_format",
"bgra"};
449 args <<
"-filter_complex" <<
"[0:v]palettegen [p]; [0:v][p] paletteuse";
451 args <<
"-loop" << (loop ?
"0" :
"-1");
466 Q_UNUSED(framesProcessed);
467 if(currentFrame > frameEnd)
473 QImage imageToExport = imageToExportBase.
copy();
476 QTransform view = cameraLayer->getViewAtFrame(currentFrame);
480 obj->paintImage(painter, currentFrame,
false,
true);
482 bytesWritten = ffmpeg.
write(reinterpret_cast<const char*>(imageToExport.
constBits()), imageToExport.
byteCount());
483 Q_ASSERT(bytesWritten == imageToExport.
byteCount());
517 ffmpeg.
start(cmd, args);
519 Status status = Status::OK;
521 dd << QStringLiteral(
"Command: %1 %2").arg(cmd).arg(args.
join(
' '));
532 qDebug() <<
"[ffmpeg]" << s;
536 if(output.startsWith(
"frame="))
538 QString frame = output.
mid(6, output.indexOf(
' '));
540 bool shouldContinue = progress(frame.
toInt());
547 return Status::CANCELED;
556 qDebug() <<
"[ffmpeg]" << s;
562 status = Status::FAIL;
563 status.setTitle(
QObject::tr(
"Something went wrong"));
564 status.setDescription(
QObject::tr(
"Looks like our video backend did not exit normally. Your movie may not have exported correctly. Please try again and report this if it persists."));
567 status.setDetails(dd);
573 qDebug() <<
"ERROR: Could not execute FFmpeg.";
574 status = Status::FAIL;
575 status.setTitle(
QObject::tr(
"Something went wrong"));
576 status.setDescription(
QObject::tr(
"Couldn't start the video backend, please try again."));
577 status.setDetails(dd);
633 ffmpeg.
start(cmd, args);
635 Status status = Status::OK;
637 dd << QStringLiteral(
"Command: %1 %2").arg(cmd).arg(args.
join(
' '));
640 int framesGenerated = 0;
641 int lastFrameProcessed = 0;
642 const int frameStart = mDesc.startFrame;
643 const int frameEnd = mDesc.endFrame;
650 return Status::CANCELED;
655 int framesProcessed = -1;
662 qDebug() <<
"[ffmpeg]" << s;
665 if(output.startsWith(
"frame="))
667 lastFrameProcessed = framesProcessed = output.mid(6, output.indexOf(
' ')).toInt();
676 while(writeFrame(ffmpeg, framesProcessed))
680 const float percentGenerated = framesGenerated /
static_cast<float>(frameEnd - frameStart);
681 const float percentConverted = lastFrameProcessed /
static_cast<float>(frameEnd - frameStart);
682 progress((percentGenerated + percentConverted) / 2);
684 const float percentGenerated = framesGenerated /
static_cast<float>(frameEnd - frameStart);
685 const float percentConverted = lastFrameProcessed /
static_cast<float>(frameEnd - frameStart);
686 progress((percentGenerated + percentConverted) / 2);
693 qDebug() <<
"[ffmpeg]" << s;
699 status = Status::FAIL;
700 status.setTitle(
QObject::tr(
"Something went wrong"));
701 status.setDescription(
QObject::tr(
"Looks like our video backend did not exit normally. Your movie may not have exported correctly. Please try again and report this if it persists."));
704 status.setDetails(dd);
710 qDebug() <<
"ERROR: Could not execute FFmpeg.";
711 status = Status::FAIL;
712 status.setTitle(
QObject::tr(
"Something went wrong"));
713 status.setDescription(
QObject::tr(
"Couldn't start the video backend, please try again."));
714 status.setDetails(dd);
723 b &= (!desc.strFileName.
isEmpty());
724 b &= (desc.startFrame > 0);
725 b &= (desc.endFrame >= desc.startFrame);
727 b &= (!desc.strCameraName.
isEmpty());
729 return b ? Status::OK : Status::INVALID_ARGUMENT;
QString & append(QChar ch)
bool isWritable() const const
virtual bool waitForReadyRead(int msecs) override
QString filePath(const QString &fileName) const const
QString join(const QString &separator) const const
bool isValid() const const
bool exists() const const
int byteCount() const const
QImage copy(const QRect &rectangle) const const
QString tr(const char *sourceText, const char *disambiguation, int n)
Status assembleAudio(const Object *obj, QString ffmpegPath, std::function< void(float)> progress)
Combines all audio tracks in obj into a single file.
void setWindow(const QRect &rectangle)
QString number(int n, int base)
bool exists() const const
void fill(uint pixelValue)
int toInt(bool *ok, int base) const const
bool isEmpty() const const
const uchar * constBits() const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
Status run(const Object *obj, const ExportMovieDesc &desc, std::function< void(float, float)> majorProgress, std::function< void(float)> minorProgress, std::function< void(QString)> progressMessage)
Begin exporting the movie described by exportDesc.
QString path() const const
bool waitForStarted(int msecs)
static Status executeFFmpeg(const QString &cmd, const QStringList &args, std::function< bool(int)> progress)
Runs the specified command (should be ffmpeg) and allows for progress feedback.
void setProcessChannelMode(QProcess::ProcessChannelMode mode)
QString mid(int position, int n) const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
qint64 write(const char *data, qint64 maxSize)
Status generateGif(const Object *obj, QString ffmpeg, QString strOut, std::function< void(float)> progress)
Exports obj to a gif image at strOut using FFmpeg.
void setReadChannel(QProcess::ProcessChannel channel)
QProcess::ExitStatus exitStatus() const const
int exitCode() const const
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
QProcess::ProcessState state() const const
Status generateMovie(const Object *obj, QString ffmpegPath, QString strOutputFile, std::function< void(float)> progress)
Exports obj to a movie image at strOut using FFmpeg.
Status executeFFMpegPipe(const QString &cmd, const QStringList &args, std::function< void(float)> progress, std::function< bool(QProcess &, int)> writeFrame)
Runs the specified command (should be ffmpeg), and lets writeFrame pipe data into it 1 frame at a tim...
bool waitForFinished(int msecs)