diff --git a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp index 6ee7cd8e8a..4f0b4c1939 100644 --- a/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp +++ b/plugins/extensions/animationrenderer/DlgAnimationRenderer.cpp @@ -1,406 +1,417 @@ /* * 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 #include DlgAnimationRenderer::DlgAnimationRenderer(KisDocument *doc, QWidget *parent) : KoDialog(parent) , m_image(doc->image()) , m_defaultFileName(QFileInfo(doc->url().toLocalFile()).completeBaseName()) { KisConfig cfg; setCaption(i18n("Render Animation")); setButtons(Ok | Cancel); setDefaultButton(Ok); m_page = new WdgAnimaterionRenderer(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()); m_page->intEnd->setValue(doc->image()->animationInterface()->playbackRange().end()); QStringList mimes = KisImportExportManager::mimeFilter(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); resize(m_page->sizeHint()); KoJsonTrader trader; QListlist = trader.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->grpRender, SIGNAL(toggled(bool)), this, SLOT(toggleSequenceType(bool))); connect(m_page->bnExportOptions, SIGNAL(clicked()), this, SLOT(sequenceMimeTypeSelected())); - connect(m_page->bnRenderOptions, SIGNAL(clicked()), this, SLOT(selectRenderType())); + 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->grpRender->setChecked(cfg.readEntry("AnimationRenderer/render_animation", false)); m_page->chkDeleteSequence->setChecked(cfg.readEntry("AnimationRenderer/delete_sequence", false)); m_page->cmbRenderType->setCurrentIndex(cfg.readEntry("AnimationRenderer/render_type", 0)); - - + connect(m_page->cmbRenderType, SIGNAL(currentIndexChanged(int)), this, SLOT(selectRenderType(int))); } DlgAnimationRenderer::~DlgAnimationRenderer() { KisConfig cfg; cfg.writeEntry("AnimationRenderer/render_animation", m_page->grpRender->isChecked()); cfg.writeEntry("AnimationRenderer/last_sequence_export_location", m_page->dirRequester->fileName()); cfg.writeEntry("AnimationRenderer/render_type", m_page->cmbRenderType->currentIndex()); cfg.writeEntry("AnimationRenderer/delete_sequence", m_page->chkDeleteSequence->isChecked()); 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; } KisPropertiesConfigurationSP DlgAnimationRenderer::getSequenceConfiguration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("basename", m_page->txtBasename->text()); cfg->setProperty("directory", m_page->dirRequester->fileName()); 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")); 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(); cfg->setProperty("basename", m_page->txtBasename->text()); cfg->setProperty("directory", m_page->dirRequester->fileName()); 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; } bool DlgAnimationRenderer::renderToVideo() const { return m_page->grpRender->isChecked(); } KisPropertiesConfigurationSP DlgAnimationRenderer::getVideoConfiguration() const { if (!m_page->grpRender->isChecked()) { return 0; } KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); - cfg->setProperty("filename", m_page->videoFilename->fileName()); + 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(); + } + cfg->setProperty("filename", filename); cfg->setProperty("delete_sequence", m_page->chkDeleteSequence->isChecked()); return cfg; } void DlgAnimationRenderer::setVideoConfiguration(KisPropertiesConfigurationSP /*cfg*/) { } KisPropertiesConfigurationSP DlgAnimationRenderer::getEncoderConfiguration() const { if (!m_page->grpRender->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", m_page->dirRequester->fileName()); cfg->setProperty("first_frame", m_page->intStart->value()); cfg->setProperty("last_frame", m_page->intEnd->value()); cfg->setProperty("sequence_start", m_page->sequenceStart->value()); return cfg; } void DlgAnimationRenderer::setEncoderConfiguration(KisPropertiesConfigurationSP /*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() +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()); +} + +void DlgAnimationRenderer::selectRenderOptions() { int index = m_page->cmbRenderType->currentIndex(); 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 (!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()); - if (filter) { m_encoderConfigWidget = filter->createConfigurationWidget(0, KisDocument::nativeFormatMimeType(), mimetype.toLatin1()); if (m_encoderConfigWidget) { m_encoderConfigWidget->setConfiguration(filter->lastSavedConfiguration("", mimetype.toLatin1())); KoDialog dlg(this); dlg.setMainWidget(m_encoderConfigWidget); dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); if (!dlg.exec()) { m_encoderConfigWidget->setConfiguration(filter->lastSavedConfiguration()); } dlg.setMainWidget(0); m_encoderConfigWidget->hide(); m_encoderConfigWidget->setParent(0); } } else { m_encoderConfigWidget = 0; } + } void DlgAnimationRenderer::toggleSequenceType(bool toggle) { m_page->cmbMimetype->setEnabled(!toggle); 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; } } } 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) { m_frameExportConfigWidget->setConfiguration(filter->lastSavedConfiguration("", mimetype.toLatin1())); KoDialog dlg(this); dlg.setMainWidget(m_frameExportConfigWidget); dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); if (!dlg.exec()) { m_frameExportConfigWidget->setConfiguration(filter->lastSavedConfiguration()); } m_frameExportConfigWidget->hide(); m_frameExportConfigWidget->setParent(0); dlg.setMainWidget(0); } } } void DlgAnimationRenderer::ffmpegLocationChanged(const QString &s) { KisConfig cfg; cfg.setCustomFFMpegPath(s); } void DlgAnimationRenderer::slotButtonClicked(int button) { if (button == KoDialog::Ok && m_page->grpRender->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().customFFMpegPath(); proposedPaths << customPath; proposedPaths << customPath + QDir::separator() + "ffmpeg"; proposedPaths << QDir::homePath() + "/bin/ffmpeg"; proposedPaths << "/usr/bin/ffmpeg"; proposedPaths << "/usr/local/bin/ffmpeg"; proposedPaths << KoResourcePaths::getApplicationRoot() + QDir::separator() + "bin" + QDir::separator() + "ffmpeg"; Q_FOREACH (const QString &path, proposedPaths) { if (path.isEmpty()) continue; QProcess testProcess; testProcess.start(path, QStringList() << "-version"); testProcess.waitForFinished(1000); const bool successfulStart = testProcess.state() == QProcess::NotRunning && testProcess.error() == QProcess::UnknownError; if (successfulStart) { result = path; break; } } return result; } diff --git a/plugins/extensions/animationrenderer/DlgAnimationRenderer.h b/plugins/extensions/animationrenderer/DlgAnimationRenderer.h index 8ceef43d02..9d86a7a710 100644 --- a/plugins/extensions/animationrenderer/DlgAnimationRenderer.h +++ b/plugins/extensions/animationrenderer/DlgAnimationRenderer.h @@ -1,94 +1,95 @@ /* * 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. */ #ifndef DLG_ANIMATIONRENDERERIMAGE #define DLG_ANIMATIONRENDERERIMAGE #include #include #include "ui_wdg_animationrenderer.h" #include #include class KisDocument; class KisImportExportFilter; class KisConfigWidget; class QHBoxLayout; class WdgAnimaterionRenderer : public QWidget, public Ui::WdgAnimaterionRenderer { Q_OBJECT public: WdgAnimaterionRenderer(QWidget *parent) : QWidget(parent) { setupUi(this); } }; class DlgAnimationRenderer: public KoDialog { Q_OBJECT public: DlgAnimationRenderer(KisDocument *doc, QWidget *parent = 0); ~DlgAnimationRenderer(); KisPropertiesConfigurationSP getSequenceConfiguration() const; void setSequenceConfiguration(KisPropertiesConfigurationSP cfg); KisPropertiesConfigurationSP getFrameExportConfiguration() const; bool renderToVideo() const; KisPropertiesConfigurationSP getVideoConfiguration() const; void setVideoConfiguration(KisPropertiesConfigurationSP cfg); KisPropertiesConfigurationSP getEncoderConfiguration() const; void setEncoderConfiguration(KisPropertiesConfigurationSP cfg); QSharedPointer encoderFilter() const; private Q_SLOTS: - void selectRenderType(); + void selectRenderType(int i); + void selectRenderOptions(); void toggleSequenceType(bool toggle); void sequenceMimeTypeSelected(); void ffmpegLocationChanged(const QString&); protected Q_SLOTS: void slotButtonClicked(int button); private: static QString findFFMpeg(); KisImageWSP m_image; WdgAnimaterionRenderer *m_page {0}; QList> m_renderFilters; KisConfigWidget *m_encoderConfigWidget {0}; KisConfigWidget *m_frameExportConfigWidget {0}; QString m_defaultFileName; }; #endif // DLG_ANIMATIONRENDERERIMAGE diff --git a/plugins/impex/video/kis_video_export.cpp b/plugins/impex/video/kis_video_export.cpp index 0110df08eb..84c412e7c1 100644 --- a/plugins/impex/video/kis_video_export.cpp +++ b/plugins/impex/video/kis_video_export.cpp @@ -1,133 +1,142 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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 "kis_video_export.h" #include #include #include #include #include #include #include #include #include #include #include "KisPart.h" #include #include #include #include #include #include #include #include "video_saver.h" #include "video_export_options_dialog.h" K_PLUGIN_FACTORY_WITH_JSON(KisVideoExportFactory, "krita_video_export.json", registerPlugin();) KisVideoExport::KisVideoExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisVideoExport::~KisVideoExport() { } KisImportExportFilter::ConversionStatus KisVideoExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration) { - VideoSaver videoSaver(document, batchMode()); - - if (!videoSaver.hasFFMpeg()) { - const QString warningMessage = - i18n("Could not find \'ffmpeg\' binary. Saving to video formats is impossible."); - - if (!batchMode()) { - QMessageBox::critical(KisPart::instance()->currentMainwindow(), - i18n("Video Export Error"), - warningMessage); + QString ffmpegPath = configuration->getString("ffmpeg_path"); + if (ffmpegPath.isEmpty()) { + KisConfig cfg; + ffmpegPath = cfg.customFFMpegPath(); + if (ffmpegPath.isEmpty()) { + const QString warningMessage = + i18n("Could not find \'ffmpeg\' binary. Saving to video formats is impossible."); + + if (!batchMode()) { + QMessageBox::critical(KisPart::instance()->currentMainwindow(), + i18n("Video Export Error"), + warningMessage); + } + else { + qWarning() << warningMessage; + } + return KisImportExportFilter::UsageError; } - return KisImportExportFilter::UsageError; } + VideoSaver videoSaver(document, ffmpegPath, batchMode()); + + KisImageBuilder_Result res = videoSaver.encode(filename(), configuration); if (res == KisImageBuilder_RESULT_OK) { return KisImportExportFilter::OK; } else if (res == KisImageBuilder_RESULT_CANCEL) { return KisImportExportFilter::ProgressCancelled; } else { document->setErrorMessage(i18n("FFMpeg failed to convert the image sequence. Check the logfile in your output directory for more information.")); } return KisImportExportFilter::InternalError; } KisPropertiesConfigurationSP KisVideoExport::defaultConfiguration(const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_ASSERT(!to.isEmpty()); KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); cfg->setProperty("h264PresetIndex", 5); cfg->setProperty("h264ConstantRateFactor", 23); cfg->setProperty("h264ProfileIndex", 4); cfg->setProperty("h264TuneIndex", 1); cfg->setProperty("TheoraBitrate", 5000); cfg->setProperty("CustomLineValue", ""); if (to == "video/ogg") { cfg->setProperty("CodecIndex", VideoExportOptionsDialog::CODEC_THEORA); } else if (to == "video/x-matroska" || to == "video/mp4") { cfg->setProperty("CodecIndex", VideoExportOptionsDialog::CODEC_H264); } cfg->setProperty("mimetype", to); return cfg; } KisPropertiesConfigurationSP KisVideoExport::lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const { KisPropertiesConfigurationSP cfg = defaultConfiguration(from, to); QString filterConfig = KisConfig().exportConfiguration("FFMPEG_CONFIG"); cfg->fromXML(filterConfig, false); return cfg; } KisConfigWidget *KisVideoExport::createConfigurationWidget(QWidget *parent, const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); KisConfigWidget *w = 0; if (to != "image/gif") { w = new VideoExportOptionsDialog(parent); } return w; } #include "kis_video_export.moc" diff --git a/plugins/impex/video/video_saver.cpp b/plugins/impex/video/video_saver.cpp index 2ee09b00de..a070114a82 100644 --- a/plugins/impex/video/video_saver.cpp +++ b/plugins/impex/video/video_saver.cpp @@ -1,322 +1,297 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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 "video_saver.h" #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "kis_animation_exporter.h" #include #include #include #include #include #include #include "KisPart.h" class KisFFMpegProgressWatcher : public QObject { Q_OBJECT public: KisFFMpegProgressWatcher(QFile &progressFile, int totalFrames) : m_progressFile(progressFile), m_totalFrames(totalFrames) { connect(&m_progressWatcher, SIGNAL(fileChanged(QString)), SLOT(slotFileChanged())); m_progressWatcher.addPath(m_progressFile.fileName()); } private Q_SLOTS: void slotFileChanged() { int currentFrame = -1; bool isEnded = false; while(!m_progressFile.atEnd()) { QString line = QString(m_progressFile.readLine()).remove(QChar('\n')); QStringList var = line.split("="); if (var[0] == "frame") { currentFrame = var[1].toInt(); } else if (var[0] == "progress") { isEnded = var[1] == "end"; } } if (isEnded) { emit sigProgressChanged(100); emit sigProcessingFinished(); } else { emit sigProgressChanged(100 * currentFrame / m_totalFrames); } } Q_SIGNALS: void sigProgressChanged(int percent); void sigProcessingFinished(); private: QFileSystemWatcher m_progressWatcher; QFile &m_progressFile; int m_totalFrames; }; class KisFFMpegRunner { public: KisFFMpegRunner(const QString &ffmpegPath) : m_cancelled(false), m_ffmpegPath(ffmpegPath) {} public: KisImageBuilder_Result runFFMpeg(const QStringList &specialArgs, const QString &actionName, const QString &logPath, int totalFrames) { dbgFile << "runFFMpeg: specialArgs" << specialArgs << "actionName" << actionName << "logPath" << logPath << "totalFrames" << totalFrames; QTemporaryFile progressFile("KritaFFmpegProgress.XXXXXX"); progressFile.open(); m_process.setStandardOutputFile(logPath); m_process.setProcessChannelMode(QProcess::MergedChannels); QStringList args; args << "-v" << "debug" << "-nostdin" << "-progress" << progressFile.fileName() << specialArgs; + qDebug() << "\t" << m_ffmpegPath << args.join(" "); + m_cancelled = false; m_process.start(m_ffmpegPath, args); return waitForFFMpegProcess(actionName, progressFile, m_process, totalFrames); } void cancel() { m_cancelled = true; m_process.kill(); } private: KisImageBuilder_Result waitForFFMpegProcess(const QString &message, QFile &progressFile, QProcess &ffmpegProcess, int totalFrames) { KisFFMpegProgressWatcher watcher(progressFile, totalFrames); QProgressDialog progress(message, "", 0, 0, KisPart::instance()->currentMainwindow()); progress.setWindowModality(Qt::ApplicationModal); progress.setCancelButton(0); progress.setMinimumDuration(0); progress.setValue(0); - progress.setRange(0,100); + progress.setRange(0, 100); QEventLoop loop; loop.connect(&watcher, SIGNAL(sigProcessingFinished()), SLOT(quit())); loop.connect(&ffmpegProcess, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(quit())); loop.connect(&watcher, SIGNAL(sigProgressChanged(int)), &progress, SLOT(setValue(int))); loop.exec(); // wait for some errorneous case ffmpegProcess.waitForFinished(5000); KisImageBuilder_Result retval = KisImageBuilder_RESULT_OK; if (ffmpegProcess.state() != QProcess::NotRunning) { // sorry... ffmpegProcess.kill(); retval = KisImageBuilder_RESULT_FAILURE; } else if (m_cancelled) { retval = KisImageBuilder_RESULT_CANCEL; } else if (ffmpegProcess.exitCode()) { retval = KisImageBuilder_RESULT_FAILURE; } return retval; } private: QProcess m_process; bool m_cancelled; QString m_ffmpegPath; }; -VideoSaver::VideoSaver(KisDocument *doc, bool batchMode) +VideoSaver::VideoSaver(KisDocument *doc, const QString &ffmpegPath, bool batchMode) : m_image(doc->image()) , m_doc(doc) , m_batchMode(batchMode) - , m_ffmpegPath(findFFMpeg()) - , m_runner(new KisFFMpegRunner(m_ffmpegPath)) + , m_ffmpegPath(ffmpegPath) + , m_runner(new KisFFMpegRunner(ffmpegPath)) { } VideoSaver::~VideoSaver() { } KisImageSP VideoSaver::image() { return m_image; } bool VideoSaver::hasFFMpeg() const { return !m_ffmpegPath.isEmpty(); } -QString VideoSaver::findFFMpeg() +KisImageBuilder_Result VideoSaver::encode(const QString &filename, KisPropertiesConfigurationSP configuration) { - QString result; - - QStringList proposedPaths; - - QString customPath = KisConfig().customFFMpegPath(); - proposedPaths << customPath; - proposedPaths << customPath + QDir::separator() + "ffmpeg"; - proposedPaths << "ffmpeg"; - proposedPaths << KoResourcePaths::getApplicationRoot() + - QDir::separator() + "bin" + QDir::separator() + "ffmpeg"; + dbgFile << "ffmpeg" << m_ffmpegPath << "filename" << filename << "configuration" << configuration->toXML(); - Q_FOREACH (const QString &path, proposedPaths) { - if (path.isEmpty()) continue; - - QProcess testProcess; - testProcess.start(path, QStringList() << "-version"); - testProcess.waitForFinished(1000); - - const bool successfulStart = - testProcess.state() == QProcess::NotRunning && - testProcess.error() == QProcess::UnknownError; - - if (successfulStart) { - result = path; - break; + if (m_ffmpegPath.isEmpty()) { + m_ffmpegPath = configuration->getString("ffmpeg_path"); + if (!QFileInfo(m_ffmpegPath).exists()) { + return KisImageBuilder_RESULT_FAILURE; } } - return result; -} - -KisImageBuilder_Result VideoSaver::encode(const QString &filename, KisPropertiesConfigurationSP configuration) -{ - - //qDebug() << "ffmpeg" << m_ffmpegPath << "filename" << filename << "configuration" << configuration->toXML(); - - if (m_ffmpegPath.isEmpty()) return KisImageBuilder_RESULT_FAILURE; - KisImageBuilder_Result result = KisImageBuilder_RESULT_OK; KisImageAnimationInterface *animation = m_image->animationInterface(); const KisTimeRange fullRange = animation->fullClipRange(); const KisTimeRange clipRange(configuration->getInt("firstframe", fullRange.start()), configuration->getInt("lastFrame"), fullRange.end()); const int frameRate = animation->framerate(); const QDir framesDir(configuration->getString("directory")); const QString resultFile = framesDir.absolutePath() + "/" + filename; const QFileInfo info(resultFile); const QString suffix = info.suffix().toLower(); const QString palettePath = framesDir.filePath("palette.png"); const QString savedFilesMask = configuration->getString("savedFilesMask"); const QStringList additionalOptionsList = configuration->getString("customUserOptions").split(' ', QString::SkipEmptyParts); if (suffix == "gif") { { QStringList args; args << "-r" << QString::number(frameRate) << "-i" << savedFilesMask << "-vf" << "palettegen" << "-y" << palettePath; KisImageBuilder_Result result = m_runner->runFFMpeg(args, i18n("Fetching palette..."), framesDir.filePath("log_generate_palette_gif.log"), clipRange.duration() + clipRange.start()); - if (result) { + if (result != KisImageBuilder_RESULT_OK) { return result; } } { QStringList args; args << "-r" << QString::number(frameRate) << "-i" << savedFilesMask << "-i" << palettePath << "-lavfi" << "[0:v][1:v] paletteuse" << additionalOptionsList << "-y" << resultFile; + dbgFile << "savedFilesMask" << savedFilesMask << "start" << clipRange.start() << "duration" << clipRange.duration(); + KisImageBuilder_Result result = m_runner->runFFMpeg(args, i18n("Encoding frames..."), framesDir.filePath("log_encode_gif.log"), clipRange.duration() + clipRange.start()); - if (result) { + if (result != KisImageBuilder_RESULT_OK) { return result; } } } else { QStringList args; args << "-r" << QString::number(frameRate) << "-i" << savedFilesMask << additionalOptionsList << "-y" << resultFile; result = m_runner->runFFMpeg(args, i18n("Encoding frames..."), framesDir.filePath("log_encode.log"), clipRange.duration() + clipRange.start()); } return result; } void VideoSaver::cancel() { m_runner->cancel(); } #include "video_saver.moc" diff --git a/plugins/impex/video/video_saver.h b/plugins/impex/video/video_saver.h index 37dde67613..c94e01eb45 100644 --- a/plugins/impex/video/video_saver.h +++ b/plugins/impex/video/video_saver.h @@ -1,63 +1,60 @@ /* * Copyright (c) 2016 Dmitry Kazakov * * 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. */ #ifndef VIDEO_SAVER_H_ #define VIDEO_SAVER_H_ #include #include "kis_types.h" #include #include "KisImageBuilderResult.h" #include "kritavideoexport_export.h" class KisFFMpegRunner; /* The KisImageBuilder_Result definitions come from kis_png_converter.h here */ class KisDocument; class KRITAVIDEOEXPORT_EXPORT VideoSaver : public QObject { Q_OBJECT public: - VideoSaver(KisDocument* doc, bool batchMode); + VideoSaver(KisDocument* doc, const QString &ffmpegPath, bool batchMode); virtual ~VideoSaver(); KisImageSP image(); KisImageBuilder_Result encode(const QString &filename, KisPropertiesConfigurationSP configuration); bool hasFFMpeg() const; private Q_SLOTS: void cancel(); -private: - static QString findFFMpeg(); - private: KisImageSP m_image; KisDocument* m_doc; bool m_batchMode; QString m_ffmpegPath; QScopedPointer m_runner; }; #endif