File indexing completed on 2024-12-22 04:12:14
0001 /* 0002 * SPDX-FileCopyrightText: 2020 Eoin O 'Neill <eoinoneill1991@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "KisAnimationRender.h" 0008 0009 #include <QFile> 0010 #include <QFileInfo> 0011 #include <QDir> 0012 #include <QMessageBox> 0013 #include <QApplication> 0014 0015 #include "KisDocument.h" 0016 #include "KisViewManager.h" 0017 #include "KisAnimationRenderingOptions.h" 0018 #include "KisMimeDatabase.h" 0019 #include "dialogs/KisAsyncAnimationFramesSaveDialog.h" 0020 #include "kis_time_span.h" 0021 #include "KisMainWindow.h" 0022 0023 #include "krita_container_utils.h" 0024 0025 #include "KisVideoSaver.h" 0026 0027 void KisAnimationRender::render(KisDocument *doc, KisViewManager *viewManager, KisAnimationRenderingOptions encoderOptions) { 0028 const QString frameMimeType = encoderOptions.frameMimeType; 0029 const QString framesDirectory = encoderOptions.resolveAbsoluteFramesDirectory(); 0030 const QString extension = KisMimeDatabase::suffixesForMimeType(frameMimeType).first(); 0031 const QString baseFileName = QString("%1/%2.%3").arg(framesDirectory, encoderOptions.basename, extension); 0032 0033 if (mustHaveEvenDimensions(encoderOptions.videoMimeType, encoderOptions.renderMode())) { 0034 if (hasEvenDimensions(encoderOptions.width, encoderOptions.height) != true) { 0035 encoderOptions.width = encoderOptions.width + (encoderOptions.width & 0x1); 0036 encoderOptions.height = encoderOptions.height + (encoderOptions.height & 0x1); 0037 } 0038 } 0039 0040 const QSize scaledSize = doc->image()->bounds().size().scaled(encoderOptions.width, encoderOptions.height, Qt::IgnoreAspectRatio); 0041 0042 if (mustHaveEvenDimensions(encoderOptions.videoMimeType, encoderOptions.renderMode())) { 0043 if (hasEvenDimensions(scaledSize.width(), scaledSize.height()) != true) { 0044 QString type = encoderOptions.videoMimeType == "video/mp4" ? "Mpeg4 (.mp4) " : "Matroska (.mkv) "; 0045 qWarning() << type <<"requires width and height to be even, resize and try again!"; 0046 doc->setErrorMessage(i18n("%1 requires width and height to be even numbers. Please resize or crop the image before exporting.", type)); 0047 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage())); 0048 return; 0049 } 0050 } 0051 0052 const bool batchMode = false; // TODO: fetch correctly! 0053 KisAsyncAnimationFramesSaveDialog exporter(doc->image(), 0054 KisTimeSpan::fromTimeToTime(encoderOptions.firstFrame, 0055 encoderOptions.lastFrame), 0056 baseFileName, 0057 encoderOptions.sequenceStart, 0058 encoderOptions.wantsOnlyUniqueFrameSequence && !encoderOptions.shouldEncodeVideo, 0059 encoderOptions.frameExportConfig); 0060 exporter.setBatchMode(batchMode); 0061 0062 0063 KisAsyncAnimationFramesSaveDialog::Result result = 0064 exporter.regenerateRange(viewManager->mainWindow()->viewManager()); 0065 0066 // the folder could have been read-only or something else could happen 0067 if ((encoderOptions.shouldEncodeVideo || encoderOptions.wantsOnlyUniqueFrameSequence) && 0068 result == KisAsyncAnimationFramesSaveDialog::RenderComplete) { 0069 0070 const QString savedFilesMask = exporter.savedFilesMask(); 0071 0072 if (encoderOptions.shouldEncodeVideo) { 0073 const QString resultFile = encoderOptions.resolveAbsoluteVideoFilePath(); 0074 KIS_SAFE_ASSERT_RECOVER_NOOP(QFileInfo(resultFile).isAbsolute()); 0075 0076 { 0077 const QFileInfo info(resultFile); 0078 QDir dir(info.absolutePath()); 0079 0080 if (!dir.exists()) { 0081 dir.mkpath(info.absolutePath()); 0082 } 0083 KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists()); 0084 } 0085 0086 KisImportExportErrorCode res; 0087 QFile fi(resultFile); 0088 if (!fi.open(QIODevice::WriteOnly)) { 0089 qWarning() << "Could not open" << fi.fileName() << "for writing!"; 0090 res = KisImportExportErrorCannotWrite(fi.error()); 0091 } else { 0092 fi.close(); 0093 } 0094 0095 QScopedPointer<KisAnimationVideoSaver> encoder(new KisAnimationVideoSaver(doc, batchMode)); 0096 res = encoder->convert(doc, savedFilesMask, encoderOptions, batchMode); 0097 0098 if (!res.isOk()) { 0099 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", res.errorMessage())); 0100 } 0101 } 0102 0103 //File cleanup 0104 QDir d(framesDirectory); 0105 0106 if (encoderOptions.shouldDeleteSequence) { 0107 0108 QStringList savedFiles = exporter.savedFiles(); 0109 Q_FOREACH(const QString &f, savedFiles) { 0110 if (d.exists(f)) { 0111 d.remove(f); 0112 } 0113 } 0114 0115 } else if(encoderOptions.wantsOnlyUniqueFrameSequence) { 0116 0117 const QStringList fileNames = exporter.savedFiles(); 0118 const QStringList uniqueFrameNames = exporter.savedUniqueFiles(); 0119 0120 Q_FOREACH(const QString &f, fileNames) { 0121 if (!uniqueFrameNames.contains(f)) { 0122 d.remove(f); 0123 } 0124 } 0125 0126 } 0127 0128 QStringList paletteFiles = d.entryList(QStringList() << "KritaTempPalettegen_*.png", QDir::Files); 0129 0130 Q_FOREACH(const QString &f, paletteFiles) { 0131 d.remove(f); 0132 } 0133 0134 } else if (result == KisAsyncAnimationFramesSaveDialog::RenderTimedOut) { 0135 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Rendering error"), "Animation frame rendering has timed out. Output files are incomplete.\nTry to increase \"Frame Rendering Timeout\" or reduce \"Frame Rendering Clones Limit\" in Krita settings"); 0136 } else if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { 0137 QMessageBox::critical(qApp->activeWindow(), i18nc("@title:window", "Rendering error"), i18n("Failed to render animation frames! Output files are incomplete.")); 0138 } 0139 } 0140 0141 bool KisAnimationRender::mustHaveEvenDimensions(const QString &mimeType, KisAnimationRenderingOptions::RenderMode renderMode) 0142 { 0143 return (mimeType == "video/mp4" || mimeType == "video/x-matroska") && renderMode != KisAnimationRenderingOptions::RENDER_FRAMES_ONLY; 0144 } 0145 0146 bool KisAnimationRender::hasEvenDimensions(int width, int height) 0147 { 0148 return !((width & 0x1) || (height & 0x1)); 0149 }