17#include "movieimporter.h"
20#include <QTemporaryDir>
22#include <QRegularExpression>
27#include "movieexporter.h"
28#include "layermanager.h"
29#include "viewmanager.h"
30#include "soundmanager.h"
33#include "bitmapimage.h"
42MovieImporter::~MovieImporter()
48 Status status = Status::OK;
50 Layer* layer = mEditor->layers()->currentLayer();
51 if (layer->type() != Layer::BITMAP)
53 status = Status::FAIL;
54 status.setTitle(
tr(
"Bitmap only"));
55 status.setDescription(
tr(
"You need to be on the bitmap layer to import a movie clip"));
60 STATUS_CHECK(verifyFFmpegExists());
61 QString ffmpegPath = ffmpegLocation();
62 dd <<
"ffmpeg path:" << ffmpegPath;
67 QString ffprobePath = ffprobeLocation();
68 dd <<
"ffprobe path:" << ffprobePath;
71 QStringList probeArgs = {
"-v",
"error",
"-show_entries",
"format=duration",
"-of",
"default=noprint_wrappers=1:nokey=1", filePath};
74 ffprobe.
start(ffprobePath, probeArgs);
79 double seconds = output.
toDouble(&ok);
82 frames = qCeil(seconds * fps);
87 dd <<
"FFprobe output could not be parsed"
97 dd <<
"FFprobe did not exit normally"
105 qDebug() <<
"ffprobe execution failed. Details:";
106 qDebug() << dd.str();
116 ffmpeg.
start(ffmpegPath, probeArgs);
125#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
132 index = s.
indexOf(
"Duration: ");
135 QString format(
"hh:mm:ss.zzz");
138 frames = qMax(frames, curFrames);
154 status = Status::FAIL;
155 status.setTitle(
tr(
"Loading video failed"));
156 status.setDescription(
tr(
"Could not get duration from the specified video. Are you sure you are importing a valid video file?"));
157 status.setDetails(dd);
161 *frameEstimate = frames;
166 std::function<
void(
int)> progress,
167 std::function<
void(
QString)> progressMessage,
168 std::function<
bool()> askPermission)
170 if (mCanceled)
return Status::CANCELED;
172 Status status = Status::OK;
175 STATUS_CHECK(verifyFFmpegExists())
180 status = Status::FAIL;
181 status.setTitle(
tr(
"Error creating folder"));
182 status.setDescription(
tr(
"Unable to create a temporary folder, cannot import video."));
185 status.setDetails(dd);
188 mEditor->addTemporaryDir(mTempDir);
190 if (type == FileType::MOVIE) {
194 if (mEditor->currentFrame() + frames > MaxFramesBound) {
195 status = Status::FAIL;
196 status.setTitle(
tr(
"Imported movie too big!"));
197 status.setDescription(
tr(
"The movie clip is too long. Pencil2D can only hold %1 frames, but this movie would go up to about frame %2. "
198 "Please make your video shorter and try again.")
200 .arg(mEditor->currentFrame() + frames));
207 bool canProceed = askPermission();
209 if (!canProceed) {
return Status::CANCELED; }
212 auto progressCallback = [&progress,
this](
int prog) ->
bool
214 progress(prog);
return !mCanceled;
216 auto progressMsgCallback = [&progressMessage](
QString message)
218 progressMessage(message);
220 return importMovieVideo(filePath, fps, frames, progressCallback, progressMsgCallback);
222 else if (type == FileType::SOUND)
224 return importMovieAudio(filePath, [&progress,
this](
int prog) ->
bool
226 progress(prog);
return !mCanceled;
232 st.setTitle(
tr(
"Unknown error"));
233 st.setTitle(
tr(
"This should not happen..."));
238Status MovieImporter::importMovieVideo(
const QString &filePath,
int fps,
int frameEstimate,
239 std::function<
bool(
int)> progress,
240 std::function<
void(
QString)> progressMessage)
242 Status status = Status::OK;
244 Layer* layer = mEditor->layers()->currentLayer();
245 if (layer->type() != Layer::BITMAP)
247 status = Status::FAIL;
248 status.setTitle(
tr(
"Bitmap only"));
249 status.setDescription(
tr(
"You need to be on the bitmap layer to import a movie clip"));
258 progress(qFloor(qMin(frame /
static_cast<double>(frameEstimate), 1.0) * 50));
return !mCanceled; }
261 if (!status.ok() && status != Status::CANCELED) {
return status; }
263 if(mCanceled)
return Status::CANCELED;
265 progressMessage(
tr(
"Video processed, adding frames..."));
269 return generateFrames([
this, &progress](
int prog) ->
bool
271 progress(prog);
return mCanceled;
275Status MovieImporter::generateFrames(std::function<
bool(
int)> progress)
277 Status status = Status::OK;
280 auto amountOfFrames = tempDir.count();
284 importImageConfig.positionType = ImportImageConfig::CenterOfCameraFollowed;
287 status = mEditor->importImage(currentFile, importImageConfig);
293 if (mCanceled)
return Status::CANCELED;
294 progress(qFloor(50 + i /
static_cast<qreal
>(amountOfFrames) * 50));
296 currentFile = tempDir.filePath(
QString(
"%1.png").arg(i, 5, 10,
QChar(
'0')));
300 status = Status::FAIL;
301 status.setTitle(
tr(
"Failed import"));
302 status.setDescription(
tr(
"Was unable to find internal files, import unsuccessful."));
309Status MovieImporter::importMovieAudio(
const QString& filePath, std::function<
bool(
int)> progress)
311 Layer* layer = mEditor->layers()->currentLayer();
313 Status status = Status::OK;
314 if (layer->type() != Layer::SOUND)
316 status = Status::FAIL;
317 status.setTitle(
tr(
"Sound only"));
318 status.setDescription(
tr(
"You need to be on a sound layer to import the audio"));
322 int currentFrame = mEditor->currentFrame();
324 if (layer->keyExists(currentFrame))
327 if (!key->fileName().
isEmpty())
329 status = Status::FAIL;
330 status.setTitle(
tr(
"Move to an empty frame"));
331 status.setDescription(
tr(
"A frame already exists on frame: %1 Move the scrubber to a empty position on the timeline and try again").
arg(currentFrame));
334 layer->removeKeyFrame(currentFrame);
339 QStringList args{
"-i", filePath,
"-map_metadata",
"-1",
"-flags",
"bitexact",
"-fflags",
"bitexact", audioPath };
343 progress(50);
return !mCanceled;
346 if(mCanceled)
return Status::CANCELED;
349 Q_ASSERT(!layer->keyExists(currentFrame));
354 key->setSoundClipName(
QFileInfo(filePath).fileName());
355 Status st = mEditor->sound()->loadSound(key, audioPath);
359 layer->removeKeyFrame(currentFrame);
367Status MovieImporter::verifyFFmpegExists()
369 QString ffmpegPath = ffmpegLocation();
372 Status status = Status::ERROR_FFMPEG_NOT_FOUND;
373 status.setTitle(
tr(
"FFmpeg Not Found"));
374 status.setDescription(
tr(
"Please place the ffmpeg binary in plugins directory and try again"));
virtual bool addKeyFrame(int position, KeyFrame *pKeyFrame)
Adds a keyframe at the given position, unless one already exists.
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.
Status run(const QString &filePath, int fps, FileType type, std::function< void(int)> progress, std::function< void(QString)> progressMessage, std::function< bool()> askPermission)
Status estimateFrames(const QString &filePath, int fps, int *frameEstimate)
Attempts to load a video and determine it's duration.
QString filePath(const QString &fileName) const const
bool exists() const const
bool exists() const const
QString tr(const char *sourceText, const char *disambiguation, int n)
int exitCode() const const
QProcess::ExitStatus exitStatus() const const
void setProcessChannelMode(QProcess::ProcessChannelMode mode)
void setReadChannel(QProcess::ProcessChannel channel)
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
QProcess::ProcessState state() const const
bool waitForFinished(int msecs)
virtual bool waitForReadyRead(int msecs) override
bool waitForStarted(int msecs)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString & append(QChar ch)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool isEmpty() const const
QString mid(int position, int n) const const
QString number(int n, int base)
double toDouble(bool *ok) const const
int indexOf(QStringView str, int from) const const
QString errorString() const const
bool isValid() const const
QString path() const const
QTime fromString(const QString &string, Qt::DateFormat format)