File indexing completed on 2024-04-21 04:51:27

0001 /*
0002     SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "wizard.h"
0007 #include "effects/effectsrepository.hpp"
0008 #include "kdenlivesettings.h"
0009 #include "monitor/monitor.h"
0010 #include "monitor/videowidget.h"
0011 #include "profiles/profilemodel.hpp"
0012 #include "profiles/profilerepository.hpp"
0013 #include "profilesdialog.h"
0014 
0015 #include "utils/thememanager.h"
0016 #include "core.h"
0017 #include <config-kdenlive.h>
0018 
0019 #include <framework/mlt_version.h>
0020 #include <mlt++/Mlt.h>
0021 
0022 #include <KLocalizedString>
0023 #include <KMessageWidget>
0024 #include <KProcess>
0025 
0026 #include "kdenlive_debug.h"
0027 #include <QApplication>
0028 #include <QCheckBox>
0029 #include <QFile>
0030 #include <QLabel>
0031 #include <QListWidget>
0032 #include <QMimeDatabase>
0033 #include <QMimeType>
0034 #include <QPushButton>
0035 #include <QStandardPaths>
0036 #include <QTemporaryFile>
0037 #include <QTimer>
0038 #include <QXmlStreamWriter>
0039 
0040 #include <KIO/OpenUrlJob>
0041 #include <kio_version.h>
0042 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
0043 #include <KIO/JobUiDelegateFactory>
0044 #else
0045 #include <KIO/JobUiDelegate>
0046 #endif
0047 
0048 // Recommended MLT version
0049 MyWizardPage::MyWizardPage(QWidget *parent)
0050     : QWizardPage(parent)
0051 
0052 {
0053 }
0054 
0055 void MyWizardPage::setComplete(bool complete)
0056 {
0057     m_isComplete = complete;
0058 }
0059 
0060 bool MyWizardPage::isComplete() const
0061 {
0062     return m_isComplete;
0063 }
0064 
0065 Wizard::Wizard(bool autoClose, QWidget *parent)
0066     : QWizard(parent)
0067     , m_systemCheckIsOk(false)
0068     , m_brokenModule(false)
0069 {
0070     setWindowTitle(i18nc("@title:window", "Welcome to Kdenlive"));
0071     int logoHeight = int(fontMetrics().height() * 2.5);
0072     setWizardStyle(QWizard::ModernStyle);
0073     setOption(QWizard::NoBackButtonOnLastPage, true);
0074     // setOption(QWizard::ExtendedWatermarkPixmap, false);
0075     m_page = new MyWizardPage(this);
0076     m_page->setTitle(i18n("Welcome to Kdenlive %1", QString(KDENLIVE_VERSION)));
0077     m_page->setSubTitle(i18n("Using MLT %1", mlt_version_get_string()));
0078     setPixmap(QWizard::LogoPixmap, QIcon::fromTheme(QStringLiteral(":/pics/kdenlive.png")).pixmap(logoHeight, logoHeight));
0079     m_startLayout = new QVBoxLayout;
0080     m_errorWidget = new KMessageWidget(this);
0081     m_startLayout->addWidget(m_errorWidget);
0082     m_errorWidget->setCloseButtonVisible(false);
0083     m_errorWidget->hide();
0084     m_page->setLayout(m_startLayout);
0085     addPage(m_page);
0086 
0087     setButtonText(QWizard::CancelButton, i18n("Abort"));
0088     setButtonText(QWizard::FinishButton, i18n("OK"));
0089     slotCheckMlt();
0090     if (autoClose) {
0091         // This is a first run instance, check HW encoders
0092         testHwEncoders();
0093     } else {
0094         QPair<QStringList, QStringList> conversion = EffectsRepository::get()->fixDeprecatedEffects();
0095         if (conversion.first.count() > 0) {
0096             QLabel *lab = new QLabel(this);
0097             lab->setText(i18n("Converting old custom effects successful:"));
0098             m_startLayout->addWidget(lab);
0099             auto *list = new QListWidget(this);
0100             m_startLayout->addWidget(list);
0101             list->addItems(conversion.first);
0102         }
0103         if (conversion.second.count() > 0) {
0104             QLabel *lab = new QLabel(this);
0105             lab->setText(i18n("Converting old custom effects failed:"));
0106             m_startLayout->addWidget(lab);
0107             auto *list = new QListWidget(this);
0108             m_startLayout->addWidget(list);
0109             list->addItems(conversion.second);
0110         }
0111     }
0112     if (!m_errors.isEmpty() || !m_warnings.isEmpty() || (!m_infos.isEmpty())) {
0113         QLabel *lab = new QLabel(this);
0114         lab->setText(i18n("Startup error or warning, check our <a href='#'>online manual</a>."));
0115         connect(lab, &QLabel::linkActivated, this, &Wizard::slotOpenManual);
0116         m_startLayout->addWidget(lab);
0117     }
0118     if (!m_infos.isEmpty()) {
0119         auto *errorLabel = new KMessageWidget(this);
0120         errorLabel->setText(QStringLiteral("<ul>") + m_infos + QStringLiteral("</ul>"));
0121         errorLabel->setMessageType(KMessageWidget::Information);
0122         errorLabel->setWordWrap(true);
0123         errorLabel->setCloseButtonVisible(false);
0124         m_startLayout->addWidget(errorLabel);
0125         errorLabel->show();
0126     }
0127     if (!m_errors.isEmpty()) {
0128         auto *errorLabel = new KMessageWidget(this);
0129         errorLabel->setText(QStringLiteral("<ul>") + m_errors + QStringLiteral("</ul>"));
0130         errorLabel->setMessageType(KMessageWidget::Error);
0131         errorLabel->setWordWrap(true);
0132         errorLabel->setCloseButtonVisible(false);
0133         m_startLayout->addWidget(errorLabel);
0134         m_page->setComplete(false);
0135         errorLabel->show();
0136         if (!autoClose) {
0137             setButtonText(QWizard::CancelButton, i18n("Close"));
0138         }
0139     } else {
0140         m_page->setComplete(true);
0141         if (!autoClose) {
0142             setOption(QWizard::NoCancelButton, true);
0143         }
0144     }
0145     if (!m_warnings.isEmpty()) {
0146         auto *errorLabel = new KMessageWidget(this);
0147         errorLabel->setText(QStringLiteral("<ul>") + m_warnings + QStringLiteral("</ul>"));
0148         errorLabel->setMessageType(KMessageWidget::Warning);
0149         errorLabel->setWordWrap(true);
0150         errorLabel->setCloseButtonVisible(false);
0151         m_startLayout->addWidget(errorLabel);
0152         errorLabel->show();
0153     }
0154     if (m_errors.isEmpty() && m_warnings.isEmpty()) {
0155         if (autoClose) {
0156             QTimer::singleShot(0, this, &QDialog::accept);
0157             return;
0158         }
0159     }
0160     if (m_errors.isEmpty()) {
0161         // Everything is ok only some info message, show codec status
0162         auto *lab = new KMessageWidget(this);
0163         QString GPULabel = i18n("Codecs have been updated, everything seems fine.");
0164         const QStringList gpu = pCore->getMonitor(Kdenlive::ClipMonitor)->getGPUInfo();
0165         if (gpu.size() > 1 && !gpu.at(1).isEmpty()) {
0166             GPULabel.append(QLatin1Char('\n'));
0167             GPULabel.append(gpu.at(1));
0168         }
0169         lab->setText(GPULabel);
0170         lab->setMessageType(KMessageWidget::Positive);
0171         lab->setCloseButtonVisible(false);
0172         QFrame *line = new QFrame(this);
0173         line->setFrameShape(QFrame::HLine);
0174         line->setFrameShadow(QFrame::Sunken);
0175         line->setLineWidth(1);
0176         m_startLayout->addWidget(line);
0177         m_startLayout->addWidget(lab);
0178         // HW accel
0179         const QString detectedCodecs = KdenliveSettings::supportedHWCodecs().join(QLatin1Char(' '));
0180         QCheckBox *cb = new QCheckBox(i18n("VAAPI hardware acceleration"), this);
0181         m_startLayout->addWidget(cb);
0182         cb->setChecked(detectedCodecs.contains(QLatin1String("_vaapi")));
0183         QCheckBox *cbn = new QCheckBox(i18n("NVIDIA hardware acceleration"), this);
0184         m_startLayout->addWidget(cbn);
0185         cbn->setChecked(detectedCodecs.contains(QLatin1String("_nvenc")));
0186         QCheckBox *cba = new QCheckBox(i18n("AMF hardware acceleration"), this);
0187         m_startLayout->addWidget(cba);
0188         cba->setChecked(detectedCodecs.contains(QLatin1String("_amf")));
0189         QCheckBox *cbq = new QCheckBox(i18n("QSV hardware acceleration"), this);
0190         m_startLayout->addWidget(cbq);
0191         cbq->setChecked(detectedCodecs.contains(QLatin1String("_qsv")));
0192         QCheckBox *cbv = new QCheckBox(i18n("Video Toolbox hardware acceleration"), this);
0193         m_startLayout->addWidget(cbv);
0194         cbv->setChecked(detectedCodecs.contains(QLatin1String("_videotoolbox")));
0195 #if !defined(Q_OS_WIN)
0196         cba->setVisible(false);
0197         cbq->setVisible(false);
0198 #endif
0199 #if !defined(Q_OS_MAC)
0200         cbv->setVisible(false);
0201 #else
0202         cbn->setVisible(false);
0203 #endif
0204 #if defined(Q_OS_MAC) || defined(Q_OS_WIN)
0205         cb->setVisible(false);
0206 #endif
0207         QPushButton *pb = new QPushButton(i18n("Check hardware acceleration"), this);
0208         connect(pb, &QPushButton::clicked, this, [&, cb, cbn, cba, cbq, cbv, pb]() {
0209             testHwEncoders();
0210             pb->setEnabled(false);
0211             const QString detectedCodecs = KdenliveSettings::supportedHWCodecs().join(QLatin1Char(' '));
0212             cb->setChecked(detectedCodecs.contains(QLatin1String("_vaapi")));
0213             cbn->setChecked(detectedCodecs.contains(QLatin1String("_nvenc")));
0214             cba->setChecked(detectedCodecs.contains(QLatin1String("_amf")));
0215             cbq->setChecked(detectedCodecs.contains(QLatin1String("_qsv")));
0216             cbv->setChecked(detectedCodecs.contains(QLatin1String("_videotoolbox")));
0217             updateHwStatus();
0218             pb->setEnabled(true);
0219         });
0220         m_startLayout->addWidget(pb);
0221         setOption(QWizard::NoCancelButton, true);
0222         return;
0223     }
0224 }
0225 
0226 void Wizard::checkMltComponents()
0227 {
0228     m_brokenModule = false;
0229     if (!pCore->getMltRepository()) {
0230         m_errors.append(i18n("<li>Cannot start MLT backend, check your installation</li>"));
0231         m_systemCheckIsOk = false;
0232     } else {
0233         int mltVersion = QT_VERSION_CHECK(MLT_MIN_MAJOR_VERSION, MLT_MIN_MINOR_VERSION, MLT_MIN_PATCH_VERSION);
0234         int runningVersion = mlt_version_get_int();
0235         if (runningVersion < mltVersion) {
0236             m_errors.append(i18n("<li>Unsupported MLT version<br/>Please <b>upgrade</b> to %1.%2.%3</li>", MLT_MIN_MAJOR_VERSION, MLT_MIN_MINOR_VERSION,
0237                                  MLT_MIN_PATCH_VERSION));
0238             m_systemCheckIsOk = false;
0239         }
0240         // Retrieve the list of available transitions.
0241         Mlt::Properties *producers = pCore->getMltRepository()->producers();
0242         QStringList producersItemList;
0243         producersItemList.reserve(producers->count());
0244         for (int i = 0; i < producers->count(); ++i) {
0245             producersItemList << producers->get_name(i);
0246         }
0247         delete producers;
0248 
0249         // Check that we have the frei0r effects installed
0250         Mlt::Properties *filters = pCore->getMltRepository()->filters();
0251         bool hasFrei0r = false;
0252         for (int i = 0; i < filters->count(); ++i) {
0253             QString filterName = filters->get_name(i);
0254             if (filterName.startsWith(QStringLiteral("frei0r."))) {
0255                 hasFrei0r = true;
0256                 break;
0257             }
0258         }
0259         if (!hasFrei0r) {
0260             // Frei0r effects not found
0261             qDebug() << "Missing Frei0r module";
0262             m_warnings.append(
0263                 i18n("<li>Missing package: <b>Frei0r</b> effects (frei0r-plugins)<br/>provides many effects and transitions. Install recommended</li>"));
0264         }
0265 
0266         // Check that we have the avfilter effects installed
0267         bool hasAvfilter = false;
0268         for (int i = 0; i < filters->count(); ++i) {
0269             QString filterName = filters->get_name(i);
0270             if (filterName.startsWith(QStringLiteral("avfilter."))) {
0271                 hasAvfilter = true;
0272                 break;
0273             }
0274         }
0275         if (!hasAvfilter) {
0276             // AVFilter effects not found
0277             qDebug() << "Missing AVFilter module";
0278             m_warnings.append(i18n("<li>Missing package: <b>AVFilter</b><br/>provides many effects. Install recommended</li>"));
0279         } else {
0280             // Check that we have the avfilter.subtitles effects installed
0281             bool hasSubtitle = false;
0282             for (int i = 0; i < filters->count(); ++i) {
0283                 QString filterName = filters->get_name(i);
0284                 if (filterName == QStringLiteral("avfilter.subtitles")) {
0285                     hasSubtitle = true;
0286                     break;
0287                 }
0288             }
0289             if (!hasSubtitle) {
0290                 // avfilter.subtitles effect not found
0291                 qDebug() << "Missing avfilter.subtitles module";
0292                 m_warnings.append(i18n("<li>Missing filter: <b>avfilter.subtitles</b><br/>required for subtitle feature. Install recommended</li>"));
0293             }
0294         }
0295         delete filters;
0296 
0297 #if (!(defined(Q_OS_WIN) || defined(Q_OS_MAC)))
0298         // Check that we have the breeze icon theme installed
0299         const QStringList iconPaths = QIcon::themeSearchPaths();
0300         bool hasBreeze = false;
0301         for (const QString &path : iconPaths) {
0302             QDir dir(path);
0303             if (dir.exists(QStringLiteral("breeze"))) {
0304                 hasBreeze = true;
0305                 break;
0306             }
0307         }
0308         if (!hasBreeze) {
0309             // Breeze icons not found
0310             qDebug() << "Missing Breeze icons";
0311             m_warnings.append(
0312                 i18n("<li>Missing package: <b>Breeze</b> icons (breeze-icon-theme)<br/>provides many icons used in Kdenlive. Install recommended</li>"));
0313         }
0314 #endif
0315 
0316         Mlt::Properties *consumers = pCore->getMltRepository()->consumers();
0317         QStringList consumersItemList;
0318         consumersItemList.reserve(consumers->count());
0319         for (int i = 0; i < consumers->count(); ++i) {
0320             consumersItemList << consumers->get_name(i);
0321         }
0322         delete consumers;
0323         KdenliveSettings::setConsumerslist(consumersItemList);
0324         if (consumersItemList.contains(QStringLiteral("sdl2_audio"))) {
0325             // MLT >= 6.6.0 and SDL2 module
0326             KdenliveSettings::setSdlAudioBackend(QStringLiteral("sdl2_audio"));
0327             KdenliveSettings::setAudiobackend(QStringLiteral("sdl2_audio"));
0328 #if defined(Q_OS_WIN)
0329             // Use wasapi by default on Windows
0330             KdenliveSettings::setAudiodrivername(QStringLiteral("wasapi"));
0331 #endif
0332         } else if (consumersItemList.contains(QStringLiteral("sdl_audio"))) {
0333             // MLT < 6.6.0
0334             KdenliveSettings::setSdlAudioBackend(QStringLiteral("sdl_audio"));
0335             KdenliveSettings::setAudiobackend(QStringLiteral("sdl_audio"));
0336         } else if (consumersItemList.contains(QStringLiteral("rtaudio"))) {
0337             KdenliveSettings::setSdlAudioBackend(QStringLiteral("sdl2_audio"));
0338             KdenliveSettings::setAudiobackend(QStringLiteral("rtaudio"));
0339         } else {
0340             // SDL module
0341             m_errors.append(i18n("<li>Missing MLT module: <b>sdl</b> or <b>rtaudio</b><br/>required for audio output</li>"));
0342             m_systemCheckIsOk = false;
0343         }
0344 
0345         Mlt::Consumer *consumer = nullptr;
0346         Mlt::Profile p;
0347         // XML module
0348         if (consumersItemList.contains(QStringLiteral("xml"))) {
0349             consumer = new Mlt::Consumer(p, "xml");
0350         }
0351         if (consumer == nullptr || !consumer->is_valid()) {
0352             qDebug() << "Missing XML MLT module";
0353             m_errors.append(i18n("<li>Missing MLT module: <b>xml</b> <br/>required for audio/video</li>"));
0354             m_systemCheckIsOk = true;
0355         }
0356         // AVformat module
0357         consumer = nullptr;
0358         if (consumersItemList.contains(QStringLiteral("avformat"))) {
0359             consumer = new Mlt::Consumer(p, "avformat");
0360         }
0361         if (consumer == nullptr || !consumer->is_valid()) {
0362             qDebug() << "Missing AVFORMAT MLT module";
0363             m_warnings.append(i18n("<li>Missing MLT module: <b>avformat</b> (FFmpeg)<br/>required for audio/video</li>"));
0364             m_brokenModule = true;
0365         } else {
0366             delete consumer;
0367         }
0368 
0369         // Image module
0370         if (!producersItemList.contains(QStringLiteral("qimage")) && !producersItemList.contains(QStringLiteral("pixbuf"))) {
0371             qDebug() << "Missing image MLT module";
0372             m_warnings.append(i18n("<li>Missing MLT module: <b>qimage</b> or <b>pixbuf</b><br/>required for images and titles</li>"));
0373             m_brokenModule = true;
0374         }
0375 
0376         // Titler module
0377         if (!producersItemList.contains(QStringLiteral("kdenlivetitle"))) {
0378             qDebug() << "Missing TITLER MLT module";
0379             m_warnings.append(i18n("<li>Missing MLT module: <b>kdenlivetitle</b><br/>required to create titles</li>"));
0380             m_brokenModule = true;
0381         }
0382         // Animation module
0383         if (!producersItemList.contains(QStringLiteral("glaxnimate"))) {
0384             qDebug() << "Missing Glaxnimate MLT module";
0385             m_warnings.append(i18n("<li>Missing MLT module: <b>glaxnimate</b><br/>required to load Lottie animations</li>"));
0386             m_brokenModule = true;
0387         }
0388     }
0389     if (m_systemCheckIsOk && !m_brokenModule) {
0390         // everything is ok
0391         return;
0392     }
0393     if (!m_systemCheckIsOk || m_brokenModule) {
0394         // Something is wrong with install
0395         if (!m_systemCheckIsOk) {
0396             // WARN
0397         }
0398     } else {
0399         // OK
0400     }
0401 }
0402 
0403 // Static
0404 const QString Wizard::fixKdenliveRenderPath()
0405 {
0406     QString kdenliverenderpath;
0407     QString errorMessage;
0408     // Find path for Kdenlive renderer
0409 #ifdef Q_OS_WIN
0410     kdenliverenderpath = QCoreApplication::applicationDirPath() + QStringLiteral("/kdenlive_render.exe");
0411 #else
0412     kdenliverenderpath = QCoreApplication::applicationDirPath() + QStringLiteral("/kdenlive_render");
0413 #endif
0414     if (!QFile::exists(kdenliverenderpath)) {
0415         const QStringList mltpath({QFileInfo(KdenliveSettings::meltpath()).canonicalPath(), qApp->applicationDirPath()});
0416         kdenliverenderpath = QStandardPaths::findExecutable(QStringLiteral("kdenlive_render"), mltpath);
0417         if (kdenliverenderpath.isEmpty()) {
0418             kdenliverenderpath = QStandardPaths::findExecutable(QStringLiteral("kdenlive_render"));
0419         }
0420         if (kdenliverenderpath.isEmpty()) {
0421             errorMessage = i18n("<li>Missing app: <b>kdenlive_render</b><br/>needed for rendering.</li>");
0422         }
0423     }
0424 
0425     if (!kdenliverenderpath.isEmpty()) {
0426         KdenliveSettings::setKdenliverendererpath(kdenliverenderpath);
0427     }
0428     return errorMessage;
0429 }
0430 
0431 void Wizard::slotCheckPrograms(QString &infos, QString &warnings, QString &errors)
0432 {
0433     // Check first in same folder as melt exec
0434     const QStringList mltpath({QFileInfo(KdenliveSettings::meltpath()).canonicalPath(), qApp->applicationDirPath()});
0435     QString exepath;
0436     if (KdenliveSettings::ffmpegpath().isEmpty() || !QFileInfo::exists(KdenliveSettings::ffmpegpath())) {
0437         exepath = QStandardPaths::findExecutable(QString("ffmpeg%1").arg(FFMPEG_SUFFIX), mltpath);
0438         if (exepath.isEmpty()) {
0439             exepath = QStandardPaths::findExecutable(QString("ffmpeg%1").arg(FFMPEG_SUFFIX));
0440         }
0441         qDebug() << "Found FFMpeg binary: " << exepath;
0442         if (exepath.isEmpty()) {
0443             // Check for libav version
0444             exepath = QStandardPaths::findExecutable(QStringLiteral("avconv"));
0445             if (exepath.isEmpty()) {
0446                 warnings.append(i18n("<li>Missing app: <b>ffmpeg</b><br/>required for proxy clips and transcoding</li>"));
0447             }
0448         }
0449     }
0450     QString playpath;
0451     if (KdenliveSettings::ffplaypath().isEmpty() || !QFileInfo::exists(KdenliveSettings::ffplaypath())) {
0452         playpath = QStandardPaths::findExecutable(QStringLiteral("ffplay%1").arg(FFMPEG_SUFFIX), mltpath);
0453         if (playpath.isEmpty()) {
0454             playpath = QStandardPaths::findExecutable(QStringLiteral("ffplay%1").arg(FFMPEG_SUFFIX));
0455         }
0456         if (playpath.isEmpty()) {
0457             // Check for libav version
0458             playpath = QStandardPaths::findExecutable(QStringLiteral("avplay"));
0459             if (playpath.isEmpty()) {
0460                 infos.append(i18n("<li>Missing app: <b>ffplay</b><br/>recommended for some preview jobs</li>"));
0461             }
0462         }
0463     }
0464     QString probepath;
0465     if (KdenliveSettings::ffprobepath().isEmpty() || !QFileInfo::exists(KdenliveSettings::ffprobepath())) {
0466         probepath = QStandardPaths::findExecutable(QStringLiteral("ffprobe%1").arg(FFMPEG_SUFFIX), mltpath);
0467         if (probepath.isEmpty()) {
0468             probepath = QStandardPaths::findExecutable(QStringLiteral("ffprobe%1").arg(FFMPEG_SUFFIX));
0469         }
0470         if (probepath.isEmpty()) {
0471             // Check for libav version
0472             probepath = QStandardPaths::findExecutable(QStringLiteral("avprobe"));
0473             if (probepath.isEmpty()) {
0474                 infos.append(i18n("<li>Missing app: <b>ffprobe</b><br/>recommended for extra clip analysis</li>"));
0475             }
0476         }
0477     }
0478     if (KdenliveSettings::kdenliverendererpath().isEmpty() || !QFileInfo::exists(KdenliveSettings::kdenliverendererpath())) {
0479         const QString renderError = fixKdenliveRenderPath();
0480         if (!renderError.isEmpty()) {
0481             errors.append(renderError);
0482         }
0483     }
0484 
0485     if (!exepath.isEmpty()) {
0486         KdenliveSettings::setFfmpegpath(exepath);
0487     }
0488     if (!playpath.isEmpty()) {
0489         KdenliveSettings::setFfplaypath(playpath);
0490     }
0491     if (!probepath.isEmpty()) {
0492         KdenliveSettings::setFfprobepath(probepath);
0493     }
0494 
0495     // set up some default applications
0496     QString program;
0497     if (KdenliveSettings::defaultimageapp().isEmpty()) {
0498         QString imageBinary = QStandardPaths::findExecutable(QStringLiteral("gimp"));
0499 #ifdef Q_OS_WIN
0500         if (imageBinary.isEmpty()) {
0501             imageBinary = QStandardPaths::findExecutable(QStringLiteral("gimp"), {"C:/Program Files/Gimp", "C:/Program Files (x86)/Gimp"});
0502         }
0503 #endif
0504         if (imageBinary.isEmpty()) {
0505             imageBinary = QStandardPaths::findExecutable(QStringLiteral("krita"));
0506         }
0507 #ifdef Q_OS_WIN
0508         if (imageBinary.isEmpty()) {
0509             imageBinary = QStandardPaths::findExecutable(QStringLiteral("krita"), {"C:/Program Files/Krita", "C:/Program Files (x86)/Krita"});
0510         }
0511 #endif
0512         if (!imageBinary.isEmpty()) {
0513             KdenliveSettings::setDefaultimageapp(imageBinary);
0514         }
0515     }
0516     if (KdenliveSettings::defaultaudioapp().isEmpty()) {
0517         QString audioBinary = QStandardPaths::findExecutable(QStringLiteral("audacity"));
0518 #ifdef Q_OS_WIN
0519         if (audioBinary.isEmpty()) {
0520             audioBinary = QStandardPaths::findExecutable(QStringLiteral("audacity"), {"C:/Program Files/Audacity", "C:/Program Files (x86)/Audacity"});
0521         }
0522 #endif
0523         if (audioBinary.isEmpty()) {
0524             audioBinary = QStandardPaths::findExecutable(QStringLiteral("traverso"));
0525         }
0526         if (!audioBinary.isEmpty()) {
0527             KdenliveSettings::setDefaultaudioapp(audioBinary);
0528         }
0529     }
0530     if (KdenliveSettings::glaxnimatePath().isEmpty()) {
0531         QString animBinary = QStandardPaths::findExecutable(QStringLiteral("glaxnimate"));
0532 #ifdef Q_OS_WIN
0533         if (animBinary.isEmpty()) {
0534             animBinary = QStandardPaths::findExecutable(QStringLiteral("glaxnimate"), {"C:/Program Files/Glaxnimate", "C:/Program Files (x86)/Glaxnimate"});
0535         }
0536 #endif
0537         if (!animBinary.isEmpty()) {
0538             KdenliveSettings::setGlaxnimatePath(animBinary);
0539         }
0540     }
0541 
0542     if (KdenliveSettings::mediainfopath().isEmpty() || !QFileInfo::exists(KdenliveSettings::mediainfopath())) {
0543         program = QStandardPaths::findExecutable(QStringLiteral("mediainfo"));
0544 #ifdef Q_OS_WIN
0545         if (program.isEmpty()) {
0546             program = QStandardPaths::findExecutable(QStringLiteral("mediainfo"), {"C:/Program Files/MediaInfo", "C:/Program Files (x86)/MediaInfo"});
0547         }
0548 #endif
0549         if (program.isEmpty()) {
0550             infos.append(i18n("<li>Missing app: <b>mediainfo</b><br/>optional for technical clip information</li>"));
0551         } else {
0552             KdenliveSettings::setMediainfopath(program);
0553         }
0554     }
0555 }
0556 
0557 void Wizard::installExtraMimes(const QString &baseName, const QStringList &globs)
0558 {
0559     QMimeDatabase db;
0560     QString mimefile = baseName;
0561     mimefile.replace('/', '-');
0562     QMimeType mime = db.mimeTypeForName(baseName);
0563     QStringList missingGlobs;
0564 
0565     for (const QString &glob : globs) {
0566         QMimeType type = db.mimeTypeForFile(glob, QMimeDatabase::MatchExtension);
0567         QString mimeName = type.name();
0568         if (!mimeName.contains(QStringLiteral("audio")) && !mimeName.contains(QStringLiteral("video"))) {
0569             missingGlobs << glob;
0570         }
0571     }
0572     if (missingGlobs.isEmpty()) {
0573         return;
0574     }
0575     if (!mime.isValid() || mime.isDefault()) {
0576         qCDebug(KDENLIVE_LOG) << "MIME type " << baseName << " not found";
0577     } else {
0578         QStringList extensions = mime.globPatterns();
0579         QString comment = mime.comment();
0580         for (const QString &glob : qAsConst(missingGlobs)) {
0581             if (!extensions.contains(glob)) {
0582                 extensions << glob;
0583             }
0584         }
0585         // qCDebug(KDENLIVE_LOG) << "EXTS: " << extensions;
0586         QDir mimeDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/mime/packages/"));
0587         if (!mimeDir.exists()) {
0588             mimeDir.mkpath(QStringLiteral("."));
0589         }
0590         QString packageFileName = mimeDir.absoluteFilePath(mimefile + QStringLiteral(".xml"));
0591         // qCDebug(KDENLIVE_LOG) << "INSTALLING NEW MIME TO: " << packageFileName;
0592         QFile packageFile(packageFileName);
0593         if (!packageFile.open(QIODevice::WriteOnly)) {
0594             qCCritical(KDENLIVE_LOG) << "Couldn't open" << packageFileName << "for writing";
0595             return;
0596         }
0597         QXmlStreamWriter writer(&packageFile);
0598         writer.setAutoFormatting(true);
0599         writer.writeStartDocument();
0600 
0601         const QString nsUri = QStringLiteral("http://www.freedesktop.org/standards/shared-mime-info");
0602         writer.writeDefaultNamespace(nsUri);
0603         writer.writeStartElement(QStringLiteral("mime-info"));
0604         writer.writeStartElement(nsUri, QStringLiteral("mime-type"));
0605         writer.writeAttribute(QStringLiteral("type"), baseName);
0606 
0607         if (!comment.isEmpty()) {
0608             writer.writeStartElement(nsUri, QStringLiteral("comment"));
0609             writer.writeCharacters(comment);
0610             writer.writeEndElement(); // comment
0611         }
0612 
0613         for (const QString &pattern : qAsConst(extensions)) {
0614             writer.writeStartElement(nsUri, QStringLiteral("glob"));
0615             writer.writeAttribute(QStringLiteral("pattern"), pattern);
0616             writer.writeEndElement(); // glob
0617         }
0618 
0619         writer.writeEndElement(); // mime-info
0620         writer.writeEndElement(); // mime-type
0621         writer.writeEndDocument();
0622     }
0623 }
0624 
0625 void Wizard::runUpdateMimeDatabase()
0626 {
0627     const QString localPackageDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/mime/");
0628     // Q_ASSERT(!localPackageDir.isEmpty());
0629     KProcess proc;
0630     proc << QStringLiteral("update-mime-database");
0631     proc << localPackageDir;
0632     const int exitCode = proc.execute();
0633     if (exitCode != 0) {
0634         qCWarning(KDENLIVE_LOG) << proc.program() << "exited with error code" << exitCode;
0635     }
0636 }
0637 
0638 void Wizard::adjustSettings()
0639 {
0640     QStringList globs;
0641 
0642     globs << QStringLiteral("*.mts") << QStringLiteral("*.m2t") << QStringLiteral("*.mod") << QStringLiteral("*.ts") << QStringLiteral("*.m2ts")
0643           << QStringLiteral("*.m2v");
0644     installExtraMimes(QStringLiteral("video/mpeg"), globs);
0645     globs.clear();
0646     globs << QStringLiteral("*.dv");
0647     installExtraMimes(QStringLiteral("video/dv"), globs);
0648     runUpdateMimeDatabase();
0649 }
0650 
0651 void Wizard::slotCheckMlt()
0652 {
0653     QString errorMessage;
0654     if (KdenliveSettings::meltpath().isEmpty()) {
0655         errorMessage.append(i18n("Your MLT installation cannot be found. Install MLT and restart Kdenlive.\n"));
0656     }
0657 
0658     if (!errorMessage.isEmpty()) {
0659         errorMessage.prepend(QStringLiteral("<b>%1</b><br />").arg(i18n("Fatal Error")));
0660         QLabel *pix = new QLabel();
0661         pix->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(30));
0662         QLabel *label = new QLabel(errorMessage);
0663         label->setWordWrap(true);
0664         m_startLayout->addSpacing(40);
0665         m_startLayout->addWidget(pix);
0666         m_startLayout->addWidget(label);
0667         m_systemCheckIsOk = false;
0668         // Warn
0669     } else {
0670         m_systemCheckIsOk = true;
0671     }
0672 
0673     if (m_systemCheckIsOk) {
0674         checkMltComponents();
0675     }
0676     slotCheckPrograms(m_infos, m_warnings, m_errors);
0677 }
0678 
0679 bool Wizard::isOk() const
0680 {
0681     return m_systemCheckIsOk;
0682 }
0683 
0684 void Wizard::slotOpenManual()
0685 {
0686     auto *job = new KIO::OpenUrlJob(QUrl(QStringLiteral("https://docs.kdenlive.org/troubleshooting/installation_troubleshooting.html")));
0687 #if KIO_VERSION >= QT_VERSION_CHECK(5, 98, 0)
0688     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
0689 #else
0690     job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
0691 #endif
0692     // methods like setRunExecutables, setSuggestedFilename, setEnableExternalBrowser, setFollowRedirections
0693     // exist in both classes
0694     job->start();
0695     //KIO::OpenUrlJob(QUrl(QStringLiteral("https://docs.kdenlive.org/troubleshooting/installation_troubleshooting.html")), QStringLiteral("text/html"));
0696 }
0697 
0698 bool Wizard::checkHwEncoder(const QString &name, const QStringList &args, const QTemporaryFile &file)
0699 {
0700     QProcess hwEncoders;
0701     qDebug() << "Checking" << name << "with FFmpeg args: " << args;
0702     hwEncoders.start(KdenliveSettings::ffmpegpath(), args);
0703     if (hwEncoders.waitForFinished(5000)) {
0704         if (hwEncoders.exitStatus() == QProcess::CrashExit) {
0705             qDebug() << "->" << name << "NOT supported";
0706             qDebug() << hwEncoders.readAll();
0707         } else {
0708             if (file.exists() && file.size() > 0) {
0709                 qDebug() << "->" << name << "SUPPORTED";
0710                 // success
0711                 return true;
0712             } else {
0713                 qDebug() << "->" << name << "FAILED";
0714                 //  support not enabled
0715                 qDebug() << hwEncoders.errorString();
0716                 qDebug() << hwEncoders.readAll();
0717             }
0718         }
0719     }
0720     return false;
0721 }
0722 
0723 // static
0724 QStringList Wizard::codecs()
0725 {
0726     QStringList codecs;
0727 #if defined(Q_OS_WIN)
0728     codecs << "h264_nvenc";
0729     codecs << "hevc_nvenc";
0730     codecs << "av1_nvenc";
0731     codecs << "h264_amf";
0732     codecs << "hevc_amf";
0733     codecs << "av1_amf";
0734     codecs << "h264_qsv";
0735     codecs << "hevc_qsv";
0736     codecs << "vp9_qsv";
0737     codecs << "av1_qsv";
0738 #elif defined(Q_OS_MAC)
0739     codecs << "h264_videotoolbox";
0740     codecs << "hevc_videotoolbox";
0741 #else
0742     codecs << "h264_nvenc";
0743     codecs << "hevc_nvenc";
0744     codecs << "av1_nvenc";
0745     codecs << "h264_vaapi";
0746     codecs << "hevc_vaapi";
0747     codecs << "vp9_vaapi";
0748 #endif
0749     return codecs;
0750 }
0751 
0752 void Wizard::testHwEncoders()
0753 {
0754     QProcess hwEncoders;
0755     QStringList workingCodecs;
0756     QStringList possibleCodecs = codecs();
0757     for (auto &codec : possibleCodecs) {
0758         QStringList args;
0759         args << "-hide_banner"
0760              << "-f"
0761              << "lavfi"
0762              << "-i"
0763              << "color=s=640x360"
0764              << "-frames"
0765              << "1"
0766              << "-an";
0767         if (codec.endsWith("_vaapi"))
0768             args << "-init_hw_device"
0769                  << "vaapi=vaapi0:"
0770                  << "-filter_hw_device"
0771                  << "vaapi0"
0772                  << "-vf"
0773                  << "format=nv12,hwupload";
0774         else if (codec == "hevc_qsv")
0775             args << "-load_plugin"
0776                  << "hevc_hw";
0777         args << "-c:v" << codec << "-f"
0778              << "rawvideo"
0779              << "pipe:";
0780         QProcess proc;
0781         proc.setStandardOutputFile(QProcess::nullDevice());
0782         proc.setReadChannel(QProcess::StandardError);
0783         proc.start(KdenliveSettings::ffmpegpath(), args, QIODevice::ReadOnly);
0784         bool started = proc.waitForStarted(2000);
0785         bool finished = false;
0786         QCoreApplication::processEvents();
0787         if (started) {
0788             finished = proc.waitForFinished(5000);
0789             QCoreApplication::processEvents();
0790         }
0791         if (started && finished && proc.exitStatus() == QProcess::NormalExit && !proc.exitCode()) {
0792             workingCodecs << codec;
0793         }
0794     }
0795     KdenliveSettings::setSupportedHWCodecs(workingCodecs);
0796     qDebug() << "==========\nFOUND SUPPORTED CODECS: " << KdenliveSettings::supportedHWCodecs();
0797 
0798     // Testing NVIDIA SCALER
0799     QStringList args3{"-hide_banner", "-filters"};
0800     qDebug() << "// FFMPEG ARGS: " << args3;
0801     hwEncoders.start(KdenliveSettings::ffmpegpath(), args3);
0802     bool nvScalingSupported = false;
0803     if (hwEncoders.waitForFinished(5000)) {
0804         QByteArray output = hwEncoders.readAll();
0805         hwEncoders.close();
0806         if (output.contains(QByteArray("scale_npp"))) {
0807             qDebug() << "/// ++ SCALE_NPP YES SUPPORTED ::::::";
0808             nvScalingSupported = true;
0809         } else {
0810             qDebug() << "/// ++ SCALE_NPP NOT SUPPORTED";
0811         }
0812     }
0813     KdenliveSettings::setNvScalingEnabled(nvScalingSupported);
0814 }
0815 
0816 const QString Wizard::getHWCodecFriendlyName()
0817 {
0818     const QString hwCodecs = KdenliveSettings::supportedHWCodecs().join(QLatin1Char(' '));
0819     if (hwCodecs.contains(QLatin1String("_nvenc"))) {
0820         return QStringLiteral("NVIDIA");
0821     } else if (hwCodecs.contains(QLatin1String("_vaapi"))) {
0822         return QStringLiteral("VAAPI");
0823     } else if (hwCodecs.contains(QLatin1String("_amf"))) {
0824         return QStringLiteral("AMD AMF");
0825     } else if (hwCodecs.contains(QLatin1String("_qsv"))) {
0826         return QStringLiteral("Intel QuickSync");
0827     } else if (hwCodecs.contains(QLatin1String("_videotoolbox"))) {
0828         return QStringLiteral("VideoToolBox");
0829     }
0830     return QString();
0831 }
0832 
0833 void Wizard::updateHwStatus()
0834 {
0835     auto *statusLabel = new KMessageWidget(this);
0836     bool hwEnabled = !KdenliveSettings::supportedHWCodecs().isEmpty();
0837     statusLabel->setMessageType(hwEnabled ? KMessageWidget::Positive : KMessageWidget::Information);
0838     statusLabel->setWordWrap(true);
0839     QString statusMessage;
0840     if (!hwEnabled) {
0841         statusMessage = i18n("No hardware encoders found.");
0842     } else {
0843         statusMessage = i18n("hardware encoders found and enabled (%1).", getHWCodecFriendlyName());
0844     }
0845     statusLabel->setText(statusMessage);
0846     statusLabel->setCloseButtonVisible(false);
0847     // errorLabel->setTimeout();
0848     m_startLayout->addWidget(statusLabel);
0849     statusLabel->animatedShow();
0850     QTimer::singleShot(3000, statusLabel, &KMessageWidget::animatedHide);
0851 }