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 }