File indexing completed on 2024-04-14 04:46:58

0001 /*
0002     SPDX-FileCopyrightText: 2019 Vincent Pinon <vpinon@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 #include "otioconvertions.h"
0007 
0008 #include "core.h"
0009 #include "doc/kdenlivedoc.h"
0010 #include "mainwindow.h"
0011 #include "project/projectmanager.h"
0012 
0013 #include <KLocalizedString>
0014 #include <KMessageBox>
0015 #include <QFileDialog>
0016 #include <QPlainTextEdit>
0017 #include <QStandardPaths>
0018 #include <QVBoxLayout>
0019 
0020 OtioConvertions::OtioConvertions()
0021     : AbstractPythonInterface()
0022 {
0023     addDependency(QStringLiteral("opentimelineio"), i18n("OpenTimelineIO conversion"));
0024     addScript(QStringLiteral("otiointerface.py"));
0025     connect(this, &OtioConvertions::dependenciesAvailable, this, [&]() {
0026         if (QStandardPaths::findExecutable(QStringLiteral("otioconvert")).isEmpty()) {
0027             Q_EMIT setupError(i18n("Could not find \"otioconvert\" script although it is installed through pip3.\n"
0028                                    "Please check the otio scripts are installed in a directory "
0029                                    "listed in PATH environment variable"));
0030             return;
0031         }
0032         m_importAdapters = runScript(QStringLiteral("otiointerface.py"), {"--import-suffixes"});
0033         qInfo() << "OTIO import adapters:" << m_importAdapters;
0034         if (!m_importAdapters.isEmpty()) {
0035             // no error occured so we can check export adapters as well
0036             m_exportAdapters = runScript(QStringLiteral("otiointerface.py"), {"--export-suffixes"});
0037             qInfo() << "OTIO export adapters:" << m_exportAdapters;
0038         }
0039         if (m_importAdapters.isEmpty() || m_exportAdapters.isEmpty()) {
0040             // something is wrong. Maybe it is related to an old version?
0041             proposeMaybeUpdate("opentimelineio", "0.14.0");
0042             // versions < 0.14.0 do not work on windows properly
0043             // see https://github.com/PixarAnimationStudios/OpenTimelineIO/issues/813
0044             return;
0045         }
0046         if (!(m_exportAdapters.contains("kdenlive") && m_importAdapters.contains("kdenlive"))) {
0047             Q_EMIT setupError(i18n("Your OpenTimelineIO module does not include Kdenlive adapter.\n"
0048                                    "Please install version >= 0.12\n"));
0049         }
0050     });
0051 }
0052 
0053 bool OtioConvertions::wellConfigured()
0054 {
0055     checkDependencies(false, false);
0056     return checkSetup() && missingDependencies().isEmpty() && !m_importAdapters.isEmpty() && m_importAdapters.contains("kdenlive") &&
0057            !m_exportAdapters.isEmpty() && m_exportAdapters.contains("kdenlive");
0058 }
0059 
0060 bool OtioConvertions::configureSetup()
0061 {
0062     QDialog *d = new QDialog(pCore->window());
0063     QVBoxLayout *l = new QVBoxLayout();
0064     QLabel *label = new QLabel(i18n("Configure your OpenTimelineIO setup"));
0065     QHBoxLayout *h = new QHBoxLayout();
0066     PythonDependencyMessage *msg = new PythonDependencyMessage(d, this);
0067     msg->setCloseButtonVisible(false);
0068     QToolButton *refresh = new QToolButton(d);
0069     refresh->setText(i18n("Check again"));
0070     refresh->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
0071     connect(refresh, &QToolButton::clicked, this, [&]() {
0072         if (wellConfigured()) {
0073             checkVersions();
0074         }
0075     });
0076     h->addWidget(msg);
0077     h->addWidget(refresh);
0078     QPlainTextEdit *textOutput = new QPlainTextEdit(d);
0079     textOutput->setReadOnly(true);
0080     textOutput->setFrameShape(QFrame::NoFrame);
0081     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
0082     connect(buttonBox, &QDialogButtonBox::rejected, d, &QDialog::reject);
0083     l->addWidget(label);
0084     l->addLayout(h);
0085     l->addWidget(textOutput);
0086     l->addWidget(buttonBox);
0087     d->setLayout(l);
0088 
0089     connect(this, &OtioConvertions::scriptStarted, [textOutput]() { QMetaObject::invokeMethod(textOutput, "clear"); });
0090     connect(this, &OtioConvertions::installFeedback,
0091             [textOutput](const QString jobData) { QMetaObject::invokeMethod(textOutput, "appendPlainText", Q_ARG(QString, jobData)); });
0092     connect(this, &OtioConvertions::scriptFinished, [msg]() { QMetaObject::invokeMethod(msg, "checkAfterInstall", Qt::QueuedConnection); });
0093 
0094     if (!wellConfigured()) {
0095         d->show();
0096         return true;
0097     }
0098     d->close();
0099     return false;
0100 }
0101 
0102 bool OtioConvertions::runOtioconvert(const QString &inputFile, const QString &outputFile)
0103 {
0104     QProcess convert;
0105     QString otioBinary = QStandardPaths::findExecutable(QStringLiteral("otioconvert"));
0106     if (otioBinary.isEmpty()) {
0107         KMessageBox::error(pCore->window(), i18n("OpenTimelineIO Application otioconvert not found"));
0108         return false;
0109     }
0110     convert.start(otioBinary, {"-i", inputFile, "-o", outputFile});
0111     convert.waitForFinished();
0112     if (convert.exitStatus() != QProcess::NormalExit || convert.exitCode() != 0) {
0113         KMessageBox::detailedError(pCore->window(), i18n("OpenTimelineIO Project conversion failed"), QString(convert.readAllStandardError()));
0114         return false;
0115     }
0116     pCore->displayMessage(i18n("Project conversion complete"), InformationMessage);
0117     return true;
0118 }
0119 
0120 void OtioConvertions::slotExportProject()
0121 {
0122     if (configureSetup()) {
0123         return;
0124     }
0125     QString exportFile = QFileDialog::getSaveFileName(pCore->window(), i18n("Export Project"), pCore->currentDoc()->projectDataFolder(),
0126                                                       i18n("OpenTimelineIO adapters (%1)(%1)", m_exportAdapters));
0127     if (exportFile.isNull()) {
0128         return;
0129     }
0130     QByteArray xml = pCore->projectManager()->projectSceneList("").toUtf8();
0131     if (xml.isNull()) {
0132         KMessageBox::error(pCore->window(), i18n("Project file could not be saved for export."));
0133         return;
0134     }
0135     QTemporaryFile tmp;
0136     tmp.setFileTemplate(QStringLiteral("XXXXXX.kdenlive"));
0137     if (!tmp.open() || !(tmp.write(xml) > 0)) {
0138         KMessageBox::error(pCore->window(), i18n("Unable to write to temporary kdenlive file for export: %1", tmp.fileName()));
0139         return;
0140     } else {
0141         tmp.close();
0142     }
0143     runOtioconvert(tmp.fileName(), exportFile);
0144     tmp.remove();
0145 }
0146 
0147 void OtioConvertions::slotImportProject()
0148 {
0149     if (configureSetup()) {
0150         return;
0151     }
0152     // Select foreign project to import
0153     QString importFile = QFileDialog::getOpenFileName(pCore->window(), i18n("Project to import"), pCore->currentDoc()->projectDataFolder(),
0154                                                       i18n("OpenTimelineIO adapters (%1)(%1)", m_importAdapters));
0155     if (importFile.isNull() || !QFile::exists(importFile)) {
0156         return;
0157     }
0158     // Select converted project file
0159     QString importedFile = QFileDialog::getSaveFileName(pCore->window(), i18n("Imported Project"), pCore->currentDoc()->projectDataFolder(),
0160                                                         i18n("Kdenlive project (*.kdenlive)"));
0161     if (importedFile.isNull()) {
0162         return;
0163     }
0164     if (!runOtioconvert(importFile, importedFile)) {
0165         return;
0166     }
0167     // Verify current project can be closed
0168     if (pCore->currentDoc()->isModified() &&
0169         KMessageBox::warningContinueCancel(pCore->window(), i18n("The current project has not been saved\n"
0170                                                                  "Do you want to load imported project abandoning latest changes?")) != KMessageBox::Continue) {
0171         return;
0172     }
0173     pCore->projectManager()->openFile(QUrl::fromLocalFile(importedFile));
0174 }