1 #include "tiio_ffmpeg.h"
2 #include "../toonz/tapp.h"
3 #include "tsystem.h"
4 #include "tsound.h"
5
6 #include <QProcess>
7 #include <QDir>
8 #include <QtGui/QImage>
9 #include <QRegExp>
10 #include "toonz/preferences.h"
11 #include "toonz/toonzfolders.h"
12 #include "tmsgcore.h"
13
Ffmpeg()14 Ffmpeg::Ffmpeg() {
15 m_ffmpegPath = Preferences::instance()->getFfmpegPath();
16 m_ffmpegTimeout = Preferences::instance()->getFfmpegTimeout() * 1000;
17 std::string strPath = m_ffmpegPath.toStdString();
18 m_intermediateFormat = "png";
19 }
~Ffmpeg()20 Ffmpeg::~Ffmpeg() {}
21
checkFfmpeg()22 bool Ffmpeg::checkFfmpeg() {
23 // check the user defined path in preferences first
24 QString path = Preferences::instance()->getFfmpegPath() + "/ffmpeg";
25 #if defined(_WIN32)
26 path = path + ".exe";
27 #endif
28 if (TSystem::doesExistFileOrLevel(TFilePath(path))) return true;
29
30 // check the OpenToonz root directory next
31 path = QDir::currentPath() + "/ffmpeg";
32 #if defined(_WIN32)
33 path = path + ".exe";
34 #endif
35 if (TSystem::doesExistFileOrLevel(TFilePath(path))) {
36 Preferences::instance()->setValue(ffmpegPath, QDir::currentPath());
37 return true;
38 }
39
40 // give up
41 return false;
42 }
43
checkFfprobe()44 bool Ffmpeg::checkFfprobe() {
45 // check the user defined path in preferences first
46 QString path = Preferences::instance()->getFfmpegPath() + "/ffprobe";
47 #if defined(_WIN32)
48 path = path + ".exe";
49 #endif
50 if (TSystem::doesExistFileOrLevel(TFilePath(path))) return true;
51
52 // check the OpenToonz root directory next
53 path = QDir::currentPath() + "/ffprobe";
54 #if defined(_WIN32)
55 path = path + ".exe";
56 #endif
57 if (TSystem::doesExistFileOrLevel(TFilePath(path))) {
58 Preferences::instance()->setValue(ffmpegPath, QDir::currentPath());
59 return true;
60 }
61
62 // give up
63 return false;
64 }
65
checkFormat(std::string format)66 bool Ffmpeg::checkFormat(std::string format) {
67 QString path = Preferences::instance()->getFfmpegPath() + "/ffmpeg";
68 #if defined(_WIN32)
69 path = path + ".exe";
70 #endif
71 QStringList args;
72 args << "-formats";
73 QProcess ffmpeg;
74 ffmpeg.start(path, args);
75 ffmpeg.waitForFinished();
76 QString results = ffmpeg.readAllStandardError();
77 results += ffmpeg.readAllStandardOutput();
78 ffmpeg.close();
79 std::string strResults = results.toStdString();
80 std::string::size_type n;
81 n = strResults.find(format);
82 if (n != std::string::npos)
83 return true;
84 else
85 return false;
86 }
87
getFfmpegCache()88 TFilePath Ffmpeg::getFfmpegCache() {
89 QString cacheRoot = ToonzFolder::getCacheRootFolder().getQString();
90 if (!TSystem::doesExistFileOrLevel(TFilePath(cacheRoot + "/ffmpeg"))) {
91 TSystem::mkDir(TFilePath(cacheRoot + "/ffmpeg"));
92 }
93 std::string ffmpegPath =
94 TFilePath(cacheRoot + "/ffmpeg").getQString().toStdString();
95 return TFilePath(cacheRoot + "/ffmpeg");
96 }
97
setFrameRate(double fps)98 void Ffmpeg::setFrameRate(double fps) { m_frameRate = fps; }
99
setPath(TFilePath path)100 void Ffmpeg::setPath(TFilePath path) { m_path = path; }
101
createIntermediateImage(const TImageP & img,int frameIndex)102 void Ffmpeg::createIntermediateImage(const TImageP &img, int frameIndex) {
103 m_frameCount++;
104 if (m_frameNumberOffset == -1) m_frameNumberOffset = frameIndex - 1;
105 QString tempPath = getFfmpegCache().getQString() + "//" +
106 QString::fromStdString(m_path.getName()) + "tempOut" +
107 QString::number(frameIndex - m_frameNumberOffset) + "." +
108 m_intermediateFormat;
109 std::string saveStatus = "";
110 TRasterImageP tempImage(img);
111 TRasterImage *image = (TRasterImage *)tempImage->cloneImage();
112
113 m_lx = image->getRaster()->getLx();
114 m_ly = image->getRaster()->getLy();
115 m_bpp = image->getRaster()->getPixelSize();
116 int totalBytes = m_lx * m_ly * m_bpp;
117 image->getRaster()->yMirror();
118
119 // lock raster to get data
120 image->getRaster()->lock();
121 void *buffin = image->getRaster()->getRawData();
122 assert(buffin);
123 void *buffer = malloc(totalBytes);
124 memcpy(buffer, buffin, totalBytes);
125
126 image->getRaster()->unlock();
127
128 // create QImage save format
129 QByteArray ba = m_intermediateFormat.toUpper().toLatin1();
130 const char *format = ba.data();
131
132 QImage *qi = new QImage((uint8_t *)buffer, m_lx, m_ly, QImage::Format_ARGB32);
133 qi->save(tempPath, format, -1);
134 free(buffer);
135 m_cleanUpList.push_back(tempPath);
136
137 delete qi;
138 delete image;
139 }
140
runFfmpeg(QStringList preIArgs,QStringList postIArgs,bool includesInPath,bool includesOutPath,bool overWriteFiles)141 void Ffmpeg::runFfmpeg(QStringList preIArgs, QStringList postIArgs,
142 bool includesInPath, bool includesOutPath,
143 bool overWriteFiles) {
144 QString tempName = "//" + QString::fromStdString(m_path.getName()) +
145 "tempOut%d." + m_intermediateFormat;
146 tempName = getFfmpegCache().getQString() + tempName;
147
148 QStringList args;
149 args = args + preIArgs;
150 if (!includesInPath) { // NOTE: if including the in path, it needs to be in
151 // the preIArgs argument.
152 args << "-i";
153 args << tempName;
154 }
155 if (m_hasSoundTrack) args = args + m_audioArgs;
156 args = args + postIArgs;
157 if (overWriteFiles && !includesOutPath) { // if includesOutPath is true, you
158 // need to include the overwrite in
159 // your postIArgs.
160 args << "-y";
161 }
162 if (!includesOutPath) {
163 args << m_path.getQString();
164 }
165
166 // write the file
167 QProcess ffmpeg;
168 ffmpeg.start(m_ffmpegPath + "/ffmpeg", args);
169 if (ffmpeg.waitForFinished(m_ffmpegTimeout)) {
170 QString results = ffmpeg.readAllStandardError();
171 results += ffmpeg.readAllStandardOutput();
172 int exitCode = ffmpeg.exitCode();
173 ffmpeg.close();
174 std::string strResults = results.toStdString();
175 } else {
176 DVGui::warning(
177 QObject::tr("FFmpeg timed out.\n"
178 "Please check the file for errors.\n"
179 "If the file doesn't play or is incomplete, \n"
180 "Please try raising the FFmpeg timeout in Preferences."));
181 }
182 }
183
runFfprobe(QStringList args)184 QString Ffmpeg::runFfprobe(QStringList args) {
185 QProcess ffmpeg;
186 ffmpeg.start(m_ffmpegPath + "/ffprobe", args);
187 ffmpeg.waitForFinished(m_ffmpegTimeout);
188 QString results = ffmpeg.readAllStandardError();
189 results += ffmpeg.readAllStandardOutput();
190 int exitCode = ffmpeg.exitCode();
191 ffmpeg.close();
192 // If the url cannot be opened or recognized as a multimedia file, ffprobe
193 // returns a positive exit code.
194 if (exitCode > 0) throw TImageException(m_path, "error reading info.");
195 std::string strResults = results.toStdString();
196 return results;
197 }
198
saveSoundTrack(TSoundTrack * st)199 void Ffmpeg::saveSoundTrack(TSoundTrack *st) {
200 m_sampleRate = st->getSampleRate();
201 m_channelCount = st->getChannelCount();
202 m_bitsPerSample = st->getBitPerSample();
203 // LONG count = st->getSampleCount();
204 int bufSize = st->getSampleCount() * st->getSampleSize();
205 const UCHAR *buffer = st->getRawData();
206
207 m_audioPath = getFfmpegCache().getQString() + "//" +
208 QString::fromStdString(m_path.getName()) + "tempOut.raw";
209 m_audioFormat = "s" + QString::number(m_bitsPerSample);
210 if (m_bitsPerSample > 8) m_audioFormat = m_audioFormat + "le";
211 std::string strPath = m_audioPath.toStdString();
212
213 QByteArray data;
214 data.insert(0, (char *)buffer, bufSize);
215
216 QFile file(m_audioPath);
217 file.open(QIODevice::WriteOnly);
218 file.write(data);
219 file.close();
220 m_hasSoundTrack = true;
221
222 m_audioArgs << "-f";
223 m_audioArgs << m_audioFormat;
224 m_audioArgs << "-ar";
225 m_audioArgs << QString::number(m_sampleRate);
226 m_audioArgs << "-ac";
227 m_audioArgs << QString::number(m_channelCount);
228 m_audioArgs << "-i";
229 m_audioArgs << m_audioPath;
230
231 // add file to framesWritten for cleanup
232 m_cleanUpList.push_back(m_audioPath);
233 }
234
checkFilesExist()235 bool Ffmpeg::checkFilesExist() {
236 QString ffmpegCachePath = getFfmpegCache().getQString();
237 QString tempPath = ffmpegCachePath + "//" + cleanPathSymbols() + "In0001." +
238 m_intermediateFormat;
239 if (TSystem::doesExistFileOrLevel(TFilePath(tempPath))) {
240 return true;
241 } else
242 return false;
243 }
244
getInfo()245 ffmpegFileInfo Ffmpeg::getInfo() {
246 QString ffmpegCachePath = getFfmpegCache().getQString();
247 QString tempPath = ffmpegCachePath + "//" + cleanPathSymbols() + ".txt";
248 if (QFile::exists(tempPath)) {
249 QFile infoText(tempPath);
250 infoText.open(QIODevice::ReadOnly);
251 QByteArray ba = infoText.readAll();
252 infoText.close();
253 QString text = QString::fromStdString(ba.toStdString());
254 m_lx = text.split(" ")[0].toInt();
255 m_ly = text.split(" ")[1].toInt();
256 m_frameRate = text.split(" ")[2].toDouble();
257 m_frameCount = text.split(" ")[3].toInt();
258 } else {
259 QFile infoText(tempPath);
260 getSize();
261 getFrameCount();
262 getFrameRate();
263 infoText.open(QIODevice::WriteOnly);
264 std::string infoToWrite =
265 std::to_string(m_lx) + " " + std::to_string(m_ly) + " " +
266 std::to_string(m_frameRate) + " " + std::to_string(m_frameCount);
267 int infoLength = infoToWrite.length();
268 infoText.write(infoToWrite.c_str(), infoLength);
269 infoText.close();
270 }
271 ffmpegFileInfo info;
272 info.m_lx = m_lx;
273 info.m_ly = m_ly;
274 info.m_frameRate = m_frameRate;
275 info.m_frameCount = m_frameCount;
276 return info;
277 }
getImage(int frameIndex)278 TRasterImageP Ffmpeg::getImage(int frameIndex) {
279 QString ffmpegCachePath = getFfmpegCache().getQString();
280 QString tempPath = ffmpegCachePath + "//" + cleanPathSymbols();
281 std::string tmpPath = tempPath.toStdString();
282 // QString tempPath= m_path.getQString();
283 QString number = QString("%1").arg(frameIndex, 4, 10, QChar('0'));
284 QString tempName = "In" + number + ".png";
285 tempName = tempPath + tempName;
286
287 // for debugging
288 std::string strPath = tempName.toStdString();
289 if (TSystem::doesExistFileOrLevel(TFilePath(tempName))) {
290 QImage *temp = new QImage(tempName, "PNG");
291 if (temp) {
292 QImage tempToo = temp->convertToFormat(QImage::Format_ARGB32);
293 delete temp;
294 const UCHAR *bits = tempToo.bits();
295
296 TRasterPT<TPixelRGBM32> ret;
297 ret.create(m_lx, m_ly);
298 ret->lock();
299 memcpy(ret->getRawData(), bits, m_lx * m_ly * 4);
300 ret->unlock();
301 ret->yMirror();
302 return TRasterImageP(ret);
303 }
304 }
305 return TRasterImageP();
306 }
307
getFrameRate()308 double Ffmpeg::getFrameRate() {
309 QStringList fpsArgs;
310 int fpsNum = 0, fpsDen = 0;
311 fpsArgs << "-v";
312 fpsArgs << "error";
313 fpsArgs << "-select_streams";
314 fpsArgs << "v:0";
315 fpsArgs << "-show_entries";
316 fpsArgs << "stream=r_frame_rate";
317 fpsArgs << "-of";
318 fpsArgs << "default=noprint_wrappers=1:nokey=1";
319 fpsArgs << m_path.getQString();
320 QString fpsResults = runFfprobe(fpsArgs);
321
322 QStringList fpsResultsList = fpsResults.split("/");
323 if (fpsResultsList.size() > 1) {
324 fpsNum = fpsResultsList[0].toInt();
325 fpsDen = fpsResultsList[1].toInt();
326 }
327
328 // if for some reason we don't have enough info to calculate it. Use the
329 // avg_frame_rate
330 if (!fpsDen) {
331 fpsArgs.clear();
332 fpsArgs << "-v";
333 fpsArgs << "error";
334 fpsArgs << "-select_streams";
335 fpsArgs << "v:0";
336 fpsArgs << "-show_entries";
337 fpsArgs << "stream=avg_frame_rate";
338 fpsArgs << "-of";
339 fpsArgs << "default=noprint_wrappers=1:nokey=1";
340 fpsArgs << m_path.getQString();
341 QString fpsResults = runFfprobe(fpsArgs);
342
343 fpsResultsList = fpsResults.split("/");
344 if (fpsResultsList.size() > 1) {
345 fpsNum = fpsResultsList[0].toInt();
346 fpsDen = fpsResultsList[1].toInt();
347 }
348 }
349
350 if (fpsDen > 0) {
351 m_frameRate = (double)fpsNum / (double)fpsDen;
352 }
353 return m_frameRate;
354 }
355
getSize()356 TDimension Ffmpeg::getSize() {
357 QStringList sizeArgs;
358 sizeArgs << "-v";
359 sizeArgs << "error";
360 sizeArgs << "-of";
361 sizeArgs << "flat=s=_";
362 sizeArgs << "-select_streams";
363 sizeArgs << "v:0";
364 sizeArgs << "-show_entries";
365 sizeArgs << "stream=height,width";
366 sizeArgs << m_path.getQString();
367
368 QString sizeResults = runFfprobe(sizeArgs);
369 QStringList split = sizeResults.split("\n");
370 m_lx = split[0].split("=")[1].toInt();
371 m_ly = split[1].split("=")[1].toInt();
372 return TDimension(m_lx, m_ly);
373 }
374
getFrameCount()375 int Ffmpeg::getFrameCount() {
376 // nb_read_frames from files may not be accurate. Let's calculate it based on
377 // r_frame_rate * duration
378 QStringList frameCountArgs;
379 frameCountArgs << "-v";
380 frameCountArgs << "error";
381 frameCountArgs << "-count_frames";
382 frameCountArgs << "-select_streams";
383 frameCountArgs << "v:0";
384 frameCountArgs << "-show_entries";
385 frameCountArgs << "stream=duration";
386 frameCountArgs << "-of";
387 frameCountArgs << "default=nokey=1:noprint_wrappers=1";
388 frameCountArgs << m_path.getQString();
389
390 QString frameResults = runFfprobe(frameCountArgs);
391 m_frameCount = frameResults.toDouble() * getFrameRate();
392
393 // if for some reason we don't have enough info to calculate it. Use the
394 // nb_read_frames
395 if (!m_frameCount) {
396 frameCountArgs.clear();
397 frameCountArgs << "-v";
398 frameCountArgs << "error";
399 frameCountArgs << "-count_frames";
400 frameCountArgs << "-select_streams";
401 frameCountArgs << "v:0";
402 frameCountArgs << "-show_entries";
403 frameCountArgs << "stream=nb_read_frames";
404 frameCountArgs << "-of";
405 frameCountArgs << "default=nokey=1:noprint_wrappers=1";
406 frameCountArgs << m_path.getQString();
407
408 frameResults = runFfprobe(frameCountArgs);
409 m_frameCount = frameResults.toInt();
410 }
411
412 return m_frameCount;
413 }
414
getFramesFromMovie(int frame)415 void Ffmpeg::getFramesFromMovie(int frame) {
416 QString ffmpegCachePath = getFfmpegCache().getQString();
417 QString tempPath = ffmpegCachePath + "//" + cleanPathSymbols();
418 std::string tmpPath = tempPath.toStdString();
419 QString tempName = "In%04d." + m_intermediateFormat;
420 tempName = tempPath + tempName;
421 QString tempStart;
422 if (frame == -1) {
423 tempStart = "In0001." + m_intermediateFormat;
424 tempStart = tempPath + tempStart;
425 } else {
426 QString number = QString("%1").arg(frame, 4, 10, QChar('0'));
427 tempStart = tempPath + "In" + number + "." + m_intermediateFormat;
428 }
429 QString tempBase = tempPath + "In";
430 QString addToDelete;
431 if (!TSystem::doesExistFileOrLevel(TFilePath(tempStart))) {
432 // for debugging
433 std::string strPath = tempName.toStdString();
434
435 QStringList preIFrameArgs;
436 QStringList postIFrameArgs;
437 // frameArgs << "-accurate_seek";
438 // frameArgs << "-ss";
439 // frameArgs << "0" + QString::number(frameIndex / m_info->m_frameRate);
440 preIFrameArgs << "-i";
441 preIFrameArgs << m_path.getQString();
442 postIFrameArgs << "-y";
443 postIFrameArgs << "-f";
444 postIFrameArgs << "image2";
445
446 postIFrameArgs << tempName;
447
448 runFfmpeg(preIFrameArgs, postIFrameArgs, true, true, true);
449
450 for (int i = 1; i <= m_frameCount; i++) {
451 QString number = QString("%1").arg(i, 4, 10, QChar('0'));
452 addToDelete = tempBase + number + "." + m_intermediateFormat;
453 std::string delPath = addToDelete.toStdString();
454 // addToCleanUp(addToDelete);
455 }
456 }
457 }
458
cleanPathSymbols()459 QString Ffmpeg::cleanPathSymbols() {
460 return m_path.getQString().remove(QRegExp(
461 QString::fromUtf8("[-`~!@#$%^&*()_+=|:;<>«»,.?/{}\'\"\\[\\]\\\\]")));
462 }
463
getGifFrameCount()464 int Ffmpeg::getGifFrameCount() {
465 int frame = 1;
466 QString ffmpegCachePath = getFfmpegCache().getQString();
467 QString tempPath = ffmpegCachePath + "//" + cleanPathSymbols();
468 std::string tmpPath = tempPath.toStdString();
469 QString tempName = "In%04d." + m_intermediateFormat;
470 tempName = tempPath + tempName;
471 QString tempStart;
472 tempStart = "In0001." + m_intermediateFormat;
473 tempStart = tempPath + tempStart;
474 while (TSystem::doesExistFileOrLevel(TFilePath(tempStart))) {
475 frame++;
476 QString number = QString("%1").arg(frame, 4, 10, QChar('0'));
477 tempStart = tempPath + "In" + number + "." + m_intermediateFormat;
478 }
479 return frame - 1;
480 }
481
addToCleanUp(QString path)482 void Ffmpeg::addToCleanUp(QString path) {
483 if (TSystem::doesExistFileOrLevel(TFilePath(path))) {
484 m_cleanUpList.push_back(path);
485 }
486 }
487
cleanUpFiles()488 void Ffmpeg::cleanUpFiles() {
489 for (QString path : m_cleanUpList) {
490 if (TSystem::doesExistFileOrLevel(TFilePath(path))) {
491 TSystem::deleteFile(TFilePath(path));
492 }
493 }
494 }
495
disablePrecompute()496 void Ffmpeg::disablePrecompute() {
497 Preferences::instance()->setPrecompute(false);
498 }