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 }