diff --git a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp index 27058188b6..22115851df 100644 --- a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp +++ b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp @@ -1,662 +1,662 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "DlgAnimationRenderer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_slider_spin_box.h" #include "kis_acyclic_signal_connector.h" DlgAnimationRenderer::DlgAnimationRenderer(KisDocument *doc, QWidget *parent) : KoDialog(parent) , m_image(doc->image()) , m_doc(doc) , m_defaultFileName(QFileInfo(doc->url().toLocalFile()).completeBaseName()) { KisConfig cfg(true); setCaption(i18n("Render Animation")); setButtons(Ok | Cancel); setDefaultButton(Ok); if (m_defaultFileName.isEmpty()) { m_defaultFileName = i18n("Untitled"); } m_page = new WdgAnimationRenderer(this); m_page->layout()->setMargin(0); m_page->dirRequester->setMode(KoFileDialog::OpenDirectory); QString lastLocation = cfg.readEntry("AnimationRenderer/last_sequence_export_location", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); m_page->dirRequester->setFileName(lastLocation); m_page->intStart->setMinimum(doc->image()->animationInterface()->fullClipRange().start()); m_page->intStart->setMaximum(doc->image()->animationInterface()->fullClipRange().end()); m_page->intStart->setValue(doc->image()->animationInterface()->playbackRange().start()); m_page->intEnd->setMinimum(doc->image()->animationInterface()->fullClipRange().start()); //m_page->intEnd->setMaximum(doc->image()->animationInterface()->fullClipRange().end()); // animators sometimes want to export after end frame m_page->intEnd->setValue(doc->image()->animationInterface()->playbackRange().end()); m_page->intHeight->setMinimum(1); m_page->intHeight->setMaximum(10000); m_page->intHeight->setValue(doc->image()->height()); m_page->intWidth->setMinimum(1); m_page->intWidth->setMaximum(10000); m_page->intWidth->setValue(doc->image()->width()); // try to lock the width and height being updated KisAcyclicSignalConnector *constrainsConnector = new KisAcyclicSignalConnector(this); constrainsConnector->createCoordinatedConnector()->connectBackwardInt(m_page->intWidth, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsWidth(int))); constrainsConnector->createCoordinatedConnector()->connectForwardInt(m_page->intHeight, SIGNAL(valueChanged(int)), this, SLOT(slotLockAspectRatioDimensionsHeight(int))); m_page->intFramesPerSecond->setValue(doc->image()->animationInterface()->framerate()); QFileInfo audioFileInfo(doc->image()->animationInterface()->audioChannelFileName()); const bool hasAudio = audioFileInfo.exists(); m_page->chkIncludeAudio->setEnabled(hasAudio); m_page->chkIncludeAudio->setChecked(hasAudio && !doc->image()->animationInterface()->isAudioMuted()); QStringList mimes = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimes.sort(); Q_FOREACH(const QString &mime, mimes) { QString description = KisMimeDatabase::descriptionForMimeType(mime); if (description.isEmpty()) { description = mime; } m_page->cmbMimetype->addItem(description, mime); if (mime == "image/png") { m_page->cmbMimetype->setCurrentIndex(m_page->cmbMimetype->count() - 1); } } setMainWidget(m_page); QListlist = KoJsonTrader::instance()->query("Krita/AnimationExporter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); QStringList mimetypes = json.value("X-KDE-Export").toString().split(","); Q_FOREACH(const QString &mime, mimetypes) { KLibFactory *factory = qobject_cast(loader->instance()); if (!factory) { warnUI << loader->errorString(); continue; } QObject* obj = factory->create(0); if (!obj || !obj->inherits("KisImportExportFilter")) { delete obj; continue; } QSharedPointerfilter(static_cast(obj)); if (!filter) { delete obj; continue; } m_renderFilters.append(filter); QString description = KisMimeDatabase::descriptionForMimeType(mime); if (description.isEmpty()) { description = mime; } m_page->cmbRenderType->addItem(description, mime); } } m_page->videoFilename->setMode(KoFileDialog::SaveFile); m_page->videoFilename->setStartDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); qDeleteAll(list); connect(m_page->bnExportOptions, SIGNAL(clicked()), this, SLOT(sequenceMimeTypeSelected())); connect(m_page->bnRenderOptions, SIGNAL(clicked()), this, SLOT(selectRenderOptions())); m_page->ffmpegLocation->setFileName(findFFMpeg()); m_page->ffmpegLocation->setMode(KoFileDialog::OpenFile); connect(m_page->ffmpegLocation, SIGNAL(fileSelected(QString)), this, SLOT(ffmpegLocationChanged(QString))); m_page->cmbRenderType->setCurrentIndex(cfg.readEntry("AnimationRenderer/render_type", 0)); connect(m_page->shouldExportOnlyImageSequence, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); connect(m_page->shouldExportOnlyVideo, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); connect(m_page->shouldExportAll, SIGNAL(toggled(bool)), this, SLOT(slotExportTypeChanged())); updateExportUIOptions(); // connect and cold init connect(m_page->cmbRenderType, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRenderType(int))); selectRenderType(m_page->cmbRenderType->currentIndex()); resize(m_page->sizeHint()); } DlgAnimationRenderer::~DlgAnimationRenderer() { KisConfig cfg(false); cfg.writeEntry("AnimationRenderer/last_sequence_export_location", m_page->dirRequester->fileName()); cfg.writeEntry("AnimationRenderer/render_type", m_page->cmbRenderType->currentIndex()); cfg.setCustomFFMpegPath(m_page->ffmpegLocation->fileName()); if (m_encoderConfigWidget) { m_encoderConfigWidget->setParent(0); m_encoderConfigWidget->deleteLater(); } if (m_frameExportConfigWidget) { m_frameExportConfigWidget->setParent(0); m_frameExportConfigWidget->deleteLater(); } delete m_page; } QString DlgAnimationRenderer::fetchRenderingDirectory() const { QString result = m_page->dirRequester->fileName(); if (m_page->shouldExportOnlyVideo->isChecked()) { const QFileInfo info(fetchRenderingFileName()); if (info.isAbsolute()) { result = info.absolutePath(); } } return result; } QString DlgAnimationRenderer::fetchRenderingFileName() const { QString filename = m_page->videoFilename->fileName(); if (QFileInfo(filename).completeSuffix().isEmpty()) { QString mimetype = m_page->cmbRenderType->itemData(m_page->cmbRenderType->currentIndex()).toString(); filename += "." + KisMimeDatabase::suffixesForMimeType(mimetype).first(); } if (QFileInfo(filename).isRelative()) { QDir baseDir(m_page->dirRequester->fileName()); if (m_page->shouldExportOnlyVideo->isChecked()) { QString documentDir = QFileInfo(m_doc->url().toLocalFile()).absolutePath(); if (!documentDir.isEmpty()) { baseDir = documentDir; } } filename = baseDir.absoluteFilePath(filename); } return filename; } KisPropertiesConfigurationSP DlgAnimationRenderer::getSequenceConfiguration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("basename", m_page->txtBasename->text()); cfg->setProperty("last_document_path", m_doc->localFilePath()); cfg->setProperty("directory", fetchRenderingDirectory()); cfg->setProperty("first_frame", m_page->intStart->value()); cfg->setProperty("last_frame", m_page->intEnd->value()); cfg->setProperty("sequence_start", m_page->sequenceStart->value()); cfg->setProperty("mimetype", m_page->cmbMimetype->currentData().toString()); return cfg; } void DlgAnimationRenderer::setSequenceConfiguration(KisPropertiesConfigurationSP cfg) { m_page->txtBasename->setText(cfg->getString("basename", "frame")); if (cfg->getString("last_document_path") != m_doc->localFilePath()) { cfg->removeProperty("first_frame"); cfg->removeProperty("last_frame"); cfg->removeProperty("sequence_start"); } m_page->dirRequester->setFileName(cfg->getString("directory", QStandardPaths::writableLocation(QStandardPaths::PicturesLocation))); m_page->intStart->setValue(cfg->getInt("first_frame", m_image->animationInterface()->playbackRange().start())); m_page->intEnd->setValue(cfg->getInt("last_frame", m_image->animationInterface()->playbackRange().end())); m_page->sequenceStart->setValue(cfg->getInt("sequence_start", m_image->animationInterface()->playbackRange().start())); QString mimetype = cfg->getString("mimetype"); for (int i = 0; i < m_page->cmbMimetype->count(); ++i) { if (m_page->cmbMimetype->itemData(i).toString() == mimetype) { m_page->cmbMimetype->setCurrentIndex(i); break; } } } KisPropertiesConfigurationSP DlgAnimationRenderer::getFrameExportConfiguration() const { if (m_frameExportConfigWidget) { KisPropertiesConfigurationSP cfg = m_frameExportConfigWidget->configuration(); qDebug()<setProperty("basename", m_page->txtBasename->text()); cfg->setProperty("directory", fetchRenderingDirectory()); cfg->setProperty("first_frame", m_page->intStart->value()); cfg->setProperty("last_frame", m_page->intEnd->value()); cfg->setProperty("sequence_start", m_page->sequenceStart->value()); cfg->setProperty("ffmpeg_path", m_page->ffmpegLocation->fileName()); return m_frameExportConfigWidget->configuration(); } return 0; } KisPropertiesConfigurationSP DlgAnimationRenderer::getVideoConfiguration() const { // don't continue if we are only exporting image sequence if (m_page->shouldExportOnlyImageSequence->isChecked()) { return 0; } KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("filename", fetchRenderingFileName()); cfg->setProperty("first_frame", m_page->intStart->value()); cfg->setProperty("last_frame", m_page->intEnd->value()); cfg->setProperty("sequence_start", m_page->sequenceStart->value()); // delete image sequence if we are only exporting out video cfg->setProperty("delete_sequence", m_page->shouldExportOnlyVideo->isChecked()); return cfg; } void DlgAnimationRenderer::setVideoConfiguration(KisPropertiesConfigurationSP /*cfg*/) { } KisPropertiesConfigurationSP DlgAnimationRenderer::getEncoderConfiguration() const { // don't continue if we are only exporting image sequence if (m_page->shouldExportOnlyImageSequence->isChecked()) { return 0; } KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); if (m_encoderConfigWidget) { cfg = m_encoderConfigWidget->configuration(); } cfg->setProperty("mimetype", m_page->cmbRenderType->currentData().toString()); cfg->setProperty("directory", fetchRenderingDirectory()); cfg->setProperty("first_frame", m_page->intStart->value()); cfg->setProperty("last_frame", m_page->intEnd->value()); cfg->setProperty("framerate", m_page->intFramesPerSecond->value()); int height = m_page->intHeight->value(); if (height % 2) { height += 1; } cfg->setProperty("height", height); int width = m_page->intWidth->value(); if (width % 2) { width += 1; } cfg->setProperty("width", width); cfg->setProperty("sequence_start", m_page->sequenceStart->value()); cfg->setProperty("include_audio", m_page->chkIncludeAudio->isChecked()); return cfg; } void DlgAnimationRenderer::setEncoderConfiguration(KisPropertiesConfigurationSP cfg) { m_page->intHeight->setValue(cfg->getInt("height", int(m_image->height()))); m_page->intWidth->setValue(cfg->getInt("width", int(m_image->width()))); m_page->intFramesPerSecond->setValue(cfg->getInt("framerate", int(m_image->animationInterface()->framerate()))); if (m_encoderConfigWidget) { m_encoderConfigWidget->setConfiguration(cfg); } } QSharedPointer DlgAnimationRenderer::encoderFilter() const { if (m_page->cmbRenderType->currentIndex() < m_renderFilters.size()) { return m_renderFilters[m_page->cmbRenderType->currentIndex()]; } return QSharedPointer(0); } void DlgAnimationRenderer::selectRenderType(int index) { if (index >= m_renderFilters.size()) return; QString mimetype = m_page->cmbRenderType->itemData(index).toString(); if (!m_page->videoFilename->fileName().isEmpty() && QFileInfo(m_page->videoFilename->fileName()).completeBaseName() != m_defaultFileName) { m_defaultFileName = QFileInfo(m_page->videoFilename->fileName()).completeBaseName(); } m_page->videoFilename->setMimeTypeFilters(QStringList() << mimetype, mimetype); m_page->videoFilename->setFileName(m_defaultFileName + "." + KisMimeDatabase::suffixesForMimeType(mimetype).first()); createEncoderWidget(index); } void DlgAnimationRenderer::selectRenderOptions() { int index = m_page->cmbRenderType->currentIndex(); if (m_encoderConfigWidget) { m_encoderConfigWidget->deleteLater(); m_encoderConfigWidget = 0; } if (index >= m_renderFilters.size()) return; if (!m_encoderConfigWidget) { createEncoderWidget(index); } QSharedPointer filter = m_renderFilters[index]; QString mimetype = m_page->cmbRenderType->itemData(index).toString(); if (filter) { if (m_encoderConfigWidget) { KoDialog dlg(this); dlg.setMainWidget(m_encoderConfigWidget); dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); if (!dlg.exec()) { - m_encoderConfigWidget->setConfiguration(filter->lastSavedConfiguration()); + m_encoderConfigWidget->setConfiguration(filter->lastSavedConfiguration("", mimetype.toLatin1())); } else { KisConfig(false).setExportConfiguration(mimetype.toLatin1(), m_encoderConfigWidget->configuration()); } dlg.setMainWidget(0); m_encoderConfigWidget->hide(); m_encoderConfigWidget->setParent(0); } } else { m_encoderConfigWidget = 0; } } void DlgAnimationRenderer::createEncoderWidget(int index) { if (m_encoderConfigWidget) { m_encoderConfigWidget->deleteLater(); m_encoderConfigWidget = 0; } if (index >= m_renderFilters.size()) return; QSharedPointer filter = m_renderFilters[index]; QString mimetype = m_page->cmbRenderType->itemData(index).toString(); if (filter) { m_encoderConfigWidget = filter->createConfigurationWidget(0, KisDocument::nativeFormatMimeType(), mimetype.toLatin1()); if (m_encoderConfigWidget) { m_encoderConfigWidget->setConfiguration(filter->lastSavedConfiguration("", mimetype.toLatin1())); } } } void DlgAnimationRenderer::sequenceMimeTypeSelected() { int index = m_page->cmbMimetype->currentIndex(); if (m_frameExportConfigWidget) { m_frameExportConfigWidget->deleteLater(); m_frameExportConfigWidget = 0; } QString mimetype = m_page->cmbMimetype->itemData(index).toString(); QSharedPointer filter(KisImportExportManager::filterForMimeType(mimetype, KisImportExportManager::Export)); if (filter) { m_frameExportConfigWidget = filter->createConfigurationWidget(0, KisDocument::nativeFormatMimeType(), mimetype.toLatin1()); if (m_frameExportConfigWidget) { KisPropertiesConfigurationSP config = filter->lastSavedConfiguration("", mimetype.toLatin1()); if (config) { KisImportExportManager::fillStaticExportConfigurationProperties(config, m_image); } m_frameExportConfigWidget->setConfiguration(config); KoDialog dlg(this); dlg.setMainWidget(m_frameExportConfigWidget); dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); if (!dlg.exec()) { - config = filter->lastSavedConfiguration(); + config = filter->lastSavedConfiguration("", mimetype.toLatin1()); if (config) { KisImportExportManager::fillStaticExportConfigurationProperties(config, m_image); } - m_frameExportConfigWidget->setConfiguration(filter->lastSavedConfiguration()); + m_frameExportConfigWidget->setConfiguration(filter->lastSavedConfiguration("", mimetype.toLatin1())); } m_frameExportConfigWidget->hide(); m_frameExportConfigWidget->setParent(0); dlg.setMainWidget(0); } } } void DlgAnimationRenderer::ffmpegLocationChanged(const QString &s) { KisConfig cfg(false); cfg.setCustomFFMpegPath(s); } void DlgAnimationRenderer::updateExportUIOptions() { KisConfig cfg(true); // read in what type to export to. Defaults to image sequence only QString exportType = cfg.readEntry("AnimationRenderer/export_type", "ImageSequence"); if (exportType == "ImageSequence") { m_page->shouldExportOnlyImageSequence->setChecked(true); } else if (exportType == "Video") { m_page->shouldExportOnlyVideo->setChecked(true); } else { m_page->shouldExportAll->setChecked(true); // export to both } } void DlgAnimationRenderer::slotButtonClicked(int button) { if (button == KoDialog::Ok && !m_page->shouldExportOnlyImageSequence->isChecked()) { QString ffmpeg = m_page->ffmpegLocation->fileName(); if (m_page->videoFilename->fileName().isEmpty()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("Please enter a file name to render to.")); return; } else if (ffmpeg.isEmpty()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is unknown. Please install FFmpeg first: Krita cannot render animations without FFmpeg. (www.ffmpeg.org)")); return; } else { QFileInfo fi(ffmpeg); if (!fi.exists()) { QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The location of FFmpeg is invalid. Please select the correct location of the FFmpeg executable on your system.")); return; } } } KoDialog::slotButtonClicked(button); } QString DlgAnimationRenderer::findFFMpeg() { QString result; QStringList proposedPaths; QString customPath = KisConfig(true).customFFMpegPath(); if (!customPath.isEmpty()) { proposedPaths << customPath; proposedPaths << customPath + QDir::separator() + "ffmpeg"; } #ifndef Q_OS_WIN proposedPaths << QDir::homePath() + "/bin/ffmpeg"; proposedPaths << "/usr/bin/ffmpeg"; proposedPaths << "/usr/local/bin/ffmpeg"; #endif proposedPaths << KoResourcePaths::getApplicationRoot() + QDir::separator() + "bin" + QDir::separator() + "ffmpeg"; Q_FOREACH (QString path, proposedPaths) { if (path.isEmpty()) continue; #ifdef Q_OS_WIN path = QDir::toNativeSeparators(QDir::cleanPath(path)); if (path.endsWith(QDir::separator())) { continue; } if (!path.endsWith(".exe")) { if (!QFile::exists(path)) { path += ".exe"; if (!QFile::exists(path)) { continue; } } } #endif QProcess testProcess; testProcess.start(path, QStringList() << "-version"); if (testProcess.waitForStarted(1000)) { testProcess.waitForFinished(1000); } const bool successfulStart = testProcess.state() == QProcess::NotRunning && testProcess.error() == QProcess::UnknownError; if (successfulStart) { result = path; break; } } return result; } void DlgAnimationRenderer::slotExportTypeChanged() { KisConfig cfg(false); bool willEncodeVideo = m_page->shouldExportAll->isChecked() || m_page->shouldExportOnlyVideo->isChecked(); // if a video format needs to be outputted if (willEncodeVideo) { // videos always uses PNG for creating video, so disable the ability to change the format m_page->cmbMimetype->setEnabled(false); for (int i = 0; i < m_page->cmbMimetype->count(); ++i) { if (m_page->cmbMimetype->itemData(i).toString() == "image/png") { m_page->cmbMimetype->setCurrentIndex(i); break; } } } m_page->intWidth->setVisible(willEncodeVideo); m_page->intHeight->setVisible(willEncodeVideo); m_page->intFramesPerSecond->setVisible(willEncodeVideo); m_page->fpsLabel->setVisible(willEncodeVideo); m_page->lblWidth->setVisible(willEncodeVideo); m_page->lblHeight->setVisible(willEncodeVideo); // if only exporting video if (m_page->shouldExportOnlyVideo->isChecked()) { m_page->cmbMimetype->setEnabled(false); // allow to change image format m_page->imageSequenceOptionsGroup->setVisible(false); m_page->videoOptionsGroup->setVisible(false); //shrinks the horizontal space temporarily to help resize() work m_page->videoOptionsGroup->setVisible(true); cfg.writeEntry("AnimationRenderer/export_type", "Video"); } // if only an image sequence needs to be output if (m_page->shouldExportOnlyImageSequence->isChecked()) { m_page->cmbMimetype->setEnabled(true); // allow to change image format m_page->videoOptionsGroup->setVisible(false); m_page->imageSequenceOptionsGroup->setVisible(false); m_page->imageSequenceOptionsGroup->setVisible(true); cfg.writeEntry("AnimationRenderer/export_type", "ImageSequence"); } // show all options if (m_page->shouldExportAll->isChecked() ) { m_page->imageSequenceOptionsGroup->setVisible(true); m_page->videoOptionsGroup->setVisible(true); cfg.writeEntry("AnimationRenderer/export_type", "VideoAndImageSequence"); } // for the resize to work as expected, try to hide elements first before displaying other ones. // if the widget gets bigger at any point, the resize will use that, even if elements are hidden later to make it smaller resize(m_page->sizeHint()); } void DlgAnimationRenderer::slotLockAspectRatioDimensionsWidth(int width) { Q_UNUSED(width); float aspectRatio = (float)m_image->width() / (float)m_image->height(); // update height here float newHeight = m_page->intWidth->value() / aspectRatio ; m_page->intHeight->setValue(newHeight); } void DlgAnimationRenderer::slotLockAspectRatioDimensionsHeight(int height) { Q_UNUSED(height); float aspectRatio = (float)m_image->width() / (float)m_image->height(); // update width here float newWidth = aspectRatio * m_page->intHeight->value(); m_page->intWidth->setValue(newWidth); }