17 #include "movieimporter.h"
20 #include <QTemporaryDir>
25 #include "movieexporter.h"
26 #include "layermanager.h"
27 #include "viewmanager.h"
28 #include "soundmanager.h"
30 #include "soundclip.h"
31 #include "bitmapimage.h"
40 MovieImporter::~MovieImporter()
46 Status status = Status::OK;
48 Layer* layer = mEditor->layers()->currentLayer();
49 if (layer->type() != Layer::BITMAP)
51 status = Status::FAIL;
53 status.setDescription(
QObject::tr(
"You need to be on the bitmap layer to import a movie clip"));
58 STATUS_CHECK(verifyFFmpegExists());
59 QString ffmpegPath = ffmpegLocation();
60 dd <<
"ffmpeg path:" << ffmpegPath;
65 QString ffprobePath = ffprobeLocation();
66 dd <<
"ffprobe path:" << ffprobePath;
69 QStringList probeArgs = {
"-v",
"error",
"-show_entries",
"format=duration",
"-of",
"default=noprint_wrappers=1:nokey=1", filePath};
72 ffprobe.
start(ffprobePath, probeArgs);
77 double seconds = output.
toDouble(&ok);
80 frames = qCeil(seconds * fps);
85 dd <<
"FFprobe output could not be parsed"
95 dd <<
"FFprobe did not exit normally"
103 qDebug() <<
"ffprobe execution failed. Details:";
104 qDebug() << dd.str();
114 ffmpeg.
start(ffmpegPath, probeArgs);
126 index = s.
indexOf(
"Duration: ");
129 QString format(
"hh:mm:ss.zzz");
132 frames = qMax(frames, curFrames);
148 status = Status::FAIL;
149 status.setTitle(
QObject::tr(
"Loading video failed"));
150 status.setDescription(
QObject::tr(
"Could not get duration from the specified video. Are you sure you are importing a valid video file?"));
151 status.setDetails(dd);
155 *frameEstimate = frames;
160 std::function<
void(
int)> progress,
161 std::function<
void(
QString)> progressMessage,
162 std::function<
bool()> askPermission)
164 if (mCanceled)
return Status::CANCELED;
166 Status status = Status::OK;
169 STATUS_CHECK(verifyFFmpegExists())
174 status = Status::FAIL;
175 status.setTitle(
QObject::tr(
"Error creating folder"));
176 status.setDescription(
QObject::tr(
"Unable to create a temporary folder, cannot import video."));
179 status.setDetails(dd);
182 mEditor->addTemporaryDir(mTempDir);
184 if (type == FileType::MOVIE) {
188 if (mEditor->currentFrame() + frames > MaxFramesBound) {
189 status = Status::FAIL;
190 status.setTitle(
QObject::tr(
"Imported movie too big!"));
191 status.setDescription(
QObject::tr(
"The movie clip is too long. Pencil2D can only hold %1 frames, but this movie would go up to about frame %2. "
192 "Please make your video shorter and try again.")
194 .arg(mEditor->currentFrame() + frames));
201 bool canProceed = askPermission();
203 if (!canProceed) {
return Status::CANCELED; }
206 auto progressCallback = [&progress,
this](
int prog) ->
bool
208 progress(prog);
return !mCanceled;
210 auto progressMsgCallback = [&progressMessage](
QString message)
212 progressMessage(message);
214 return importMovieVideo(filePath, fps, frames, progressCallback, progressMsgCallback);
216 else if (type == FileType::SOUND)
218 return importMovieAudio(filePath, [&progress,
this](
int prog) ->
bool
220 progress(prog);
return !mCanceled;
226 st.setTitle(
tr(
"Unknown error"));
227 st.setTitle(
tr(
"This should not happen..."));
232 Status MovieImporter::importMovieVideo(
const QString &filePath,
int fps,
int frameEstimate,
233 std::function<
bool(
int)> progress,
234 std::function<
void(
QString)> progressMessage)
236 Status status = Status::OK;
238 Layer* layer = mEditor->layers()->currentLayer();
239 if (layer->type() != Layer::BITMAP)
241 status = Status::FAIL;
243 status.setDescription(
QObject::tr(
"You need to be on the bitmap layer to import a movie clip"));
249 args <<
QDir(mTempDir->
path()).filePath(
"%05d.png");
252 progress(qFloor(qMin(frame / static_cast<double>(frameEstimate), 1.0) * 50));
return !mCanceled; }
255 if (!status.ok() && status != Status::CANCELED) {
return status; }
257 if(mCanceled)
return Status::CANCELED;
259 progressMessage(
tr(
"Video processed, adding frames..."));
263 return generateFrames([
this, &progress](
int prog) ->
bool
265 progress(prog);
return mCanceled;
269 Status MovieImporter::generateFrames(std::function<
bool(
int)> progress)
271 Layer* layer = mEditor->layers()->currentLayer();
272 Status status = Status::OK;
275 auto amountOfFrames = tempDir.
count();
283 int currentFrame = mEditor->currentFrame();
284 if(layer->keyExists(mEditor->currentFrame())) {
285 mEditor->importImage(currentFile);
289 if(imgTopLeft.isNull()) {
290 imgTopLeft.setX(static_cast<int>(viewMan->getImportView().
dx()) - bitmapImage->image()->
width() / 2);
291 imgTopLeft.setY(static_cast<int>(viewMan->getImportView().
dy()) - bitmapImage->image()->
height() / 2);
292 bitmapImage->moveTopLeft(imgTopLeft);
294 layer->addKeyFrame(currentFrame, bitmapImage);
295 mEditor->layers()->notifyAnimationLengthChanged();
296 mEditor->scrubTo(currentFrame + 1);
298 if (mCanceled)
return Status::CANCELED;
299 progress(qFloor(50 + i / static_cast<qreal>(amountOfFrames) * 50));
301 currentFile = tempDir.filePath(
QString(
"%1.png").arg(i, 5, 10,
QChar(
'0')));
305 status = Status::FAIL;
306 status.setTitle(
tr(
"Failed import"));
307 status.setDescription(
tr(
"Was unable to find internal files, import unsuccessful."));
314 Status MovieImporter::importMovieAudio(
const QString& filePath, std::function<
bool(
int)> progress)
316 Layer* layer = mEditor->layers()->currentLayer();
318 Status status = Status::OK;
319 if (layer->type() != Layer::SOUND)
321 status = Status::FAIL;
323 status.setDescription(
QObject::tr(
"You need to be on a sound layer to import the audio"));
327 int currentFrame = mEditor->currentFrame();
329 if (layer->keyExists(currentFrame))
332 if (!key->fileName().
isEmpty())
334 status = Status::FAIL;
335 status.setTitle(
QObject::tr(
"Move to an empty frame"));
336 status.setDescription(
QObject::tr(
"A frame already exists on frame: ") +
QString::number(currentFrame) +
tr(
" Move the scrubber to a empty position on the timeline and try again"));
339 layer->removeKeyFrame(currentFrame);
348 progress(50); return !mCanceled;
351 if(mCanceled) return
Status::CANCELED;
356 Q_ASSERT(!layer->keyExists(currentFrame));
359 layer->addKeyFrame(currentFrame, key);
361 Status st = mEditor->sound()->loadSound(key, audioPath);
365 layer->removeKeyFrame(currentFrame);
373 Status MovieImporter::verifyFFmpegExists()
375 QString ffmpegPath = ffmpegLocation();
378 Status status = Status::ERROR_FFMPEG_NOT_FOUND;
380 status.setDescription(
QObject::tr(
"Please place the ffmpeg binary in plugins directory and try again"));
QString & append(QChar ch)
virtual bool waitForReadyRead(int msecs) override
QTime fromString(const QString &string, Qt::DateFormat format)
bool isValid() const const
bool exists() const const
double toDouble(bool *ok) const const
QString tr(const char *sourceText, const char *disambiguation, int n)
QString number(int n, int base)
Status run(const QString &filePath, int fps, FileType type, std::function< void(int)> progress, std::function< void(QString)> progressMessage, std::function< bool()> askPermission)
QString errorString() const const
bool isEmpty() const const
int indexOf(QStringView str, int from) const const
bool exists() const const
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
Status estimateFrames(const QString &filePath, int fps, int *frameEstimate)
Attempts to load a video and determine it's duration.
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
bool waitForFinished(int msecs)