File indexing completed on 2024-12-08 04:27:21

0001 /*
0002 SPDX-FileCopyrightText: 2007 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 SPDX-FileCopyrightText: 2014 Till Theato <root@ttill.de>
0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "mltconnection.h"
0008 #include "core.h"
0009 #include "kdenlivesettings.h"
0010 #include "mainwindow.h"
0011 #include "mlt_config.h"
0012 #include <KLocalizedString>
0013 #include <KUrlRequester>
0014 #include <KUrlRequesterDialog>
0015 #include <QtConcurrent>
0016 
0017 #include <clocale>
0018 #include <lib/localeHandling.h>
0019 #include <mlt++/MltFactory.h>
0020 #include <mlt++/MltRepository.h>
0021 
0022 static void mlt_log_handler(void *service, int mlt_level, const char *format, va_list args)
0023 {
0024     if (mlt_level > mlt_log_get_level()) return;
0025     QString message;
0026     mlt_properties properties = service ? MLT_SERVICE_PROPERTIES(mlt_service(service)) : nullptr;
0027     if (properties) {
0028         char *mlt_type = mlt_properties_get(properties, "mlt_type");
0029         char *service_name = mlt_properties_get(properties, "mlt_service");
0030         char *resource = mlt_properties_get(properties, "resource");
0031         char *id = mlt_properties_get(properties, "id");
0032         if (!resource || resource[0] != '<' || resource[strlen(resource) - 1] != '>') mlt_type = mlt_properties_get(properties, "mlt_type");
0033         if (service_name)
0034             message = QString("[%1 %2 %3] ").arg(mlt_type, service_name, id);
0035         else
0036             message = QString::asprintf("[%s %p] ", mlt_type, service);
0037         if (resource) message.append(QString("\"%1\" ").arg(resource));
0038         message.append(QString::vasprintf(format, args));
0039         message.replace('\n', "");
0040         if (!strcmp(mlt_type, "filter")) {
0041             pCore->processInvalidFilter(service_name, id, message);
0042         }
0043     } else {
0044         message = QString::vasprintf(format, args);
0045         message.replace('\n', "");
0046     }
0047     qDebug() << "MLT:" << message;
0048 }
0049 
0050 std::unique_ptr<MltConnection> MltConnection::m_self;
0051 MltConnection::MltConnection(const QString &mltPath)
0052 {
0053     // Disable VDPAU that crashes in multithread environment.
0054     // TODO: make configurable
0055     setenv("MLT_NO_VDPAU", "1", 1);
0056 
0057     // After initialising the MLT factory, set the locale back from user default to C
0058     // to ensure numbers are always serialised with . as decimal point.
0059     m_repository = std::unique_ptr<Mlt::Repository>(Mlt::Factory::init());
0060 
0061 #ifdef Q_OS_FREEBSD
0062     setlocale(MLT_LC_CATEGORY, nullptr);
0063 #else
0064     std::setlocale(MLT_LC_CATEGORY, nullptr);
0065 #endif
0066 
0067     locateMeltAndProfilesPath(mltPath);
0068 
0069     // Retrieve the list of available producers.
0070     QScopedPointer<Mlt::Properties> producers(m_repository->producers());
0071     QStringList producersList;
0072     int nb_producers = producers->count();
0073     producersList.reserve(nb_producers);
0074     for (int i = 0; i < nb_producers; ++i) {
0075         producersList << producers->get_name(i);
0076     }
0077     KdenliveSettings::setProducerslist(producersList);
0078     mlt_log_set_level(MLT_LOG_WARNING);
0079     mlt_log_set_callback(mlt_log_handler);
0080     refreshLumas();
0081 }
0082 
0083 void MltConnection::construct(const QString &mltPath)
0084 {
0085     if (MltConnection::m_self) {
0086         qWarning() << "Trying to open a 2nd mlt connection";
0087         return;
0088     }
0089     MltConnection::m_self.reset(new MltConnection(mltPath));
0090 }
0091 
0092 std::unique_ptr<MltConnection> &MltConnection::self()
0093 {
0094     return MltConnection::m_self;
0095 }
0096 
0097 void MltConnection::locateMeltAndProfilesPath(const QString &mltPath)
0098 {
0099     QString profilePath = mltPath;
0100     QString appName;
0101     QString libName;
0102 #if (defined(Q_OS_WIN) || defined(Q_OS_MAC))
0103     appName = QStringLiteral("melt");
0104     libName = QStringLiteral("mlt");
0105 #else
0106     appName = QStringLiteral("melt-7");
0107     libName = QStringLiteral("mlt-7");
0108 #endif
0109     // environment variables should override other settings
0110     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && qEnvironmentVariableIsSet("MLT_PROFILES_PATH")) {
0111         profilePath = qgetenv("MLT_PROFILES_PATH");
0112         qWarning() << "profilePath from $MLT_PROFILES_PATH: " << profilePath;
0113     }
0114     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && qEnvironmentVariableIsSet("MLT_DATA")) {
0115         profilePath = qgetenv("MLT_DATA") + QStringLiteral("/profiles");
0116         qWarning() << "profilePath from $MLT_DATA: " << profilePath;
0117     }
0118     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && qEnvironmentVariableIsSet("MLT_PREFIX")) {
0119         profilePath = qgetenv("MLT_PREFIX") + QStringLiteral("/share/%1/profiles").arg(libName);
0120         qWarning() << "profilePath from $MLT_PREFIX/share: " << profilePath;
0121     }
0122 #ifdef Q_OS_MAC
0123     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && qEnvironmentVariableIsSet("MLT_PREFIX")) {
0124         profilePath = qgetenv("MLT_PREFIX") + QStringLiteral("/Resources/%1/profiles").arg(libName);
0125         qWarning() << "profilePath from $MLT_PREFIX/Resources: " << profilePath;
0126     }
0127 #endif
0128 #if (!(defined(Q_OS_WIN) || defined(Q_OS_MAC)))
0129     // stored setting should not be considered on windows as MLT is distributed with each new Kdenlive version
0130     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && !KdenliveSettings::mltpath().isEmpty()) {
0131         profilePath = KdenliveSettings::mltpath();
0132         qWarning() << "profilePath from KdenliveSetting::mltPath: " << profilePath;
0133     }
0134 #endif
0135     // try to automatically guess MLT path if installed with the same prefix as kdenlive with default data path
0136     if (profilePath.isEmpty() || !QFile::exists(profilePath)) {
0137         profilePath = QDir::cleanPath(qApp->applicationDirPath() + QStringLiteral("/../share/%1/profiles").arg(libName));
0138         qWarning() << "profilePath from appDir/../share: " << profilePath;
0139     }
0140 #ifdef Q_OS_MAC
0141     // try to automatically guess MLT path if installed with the same prefix as kdenlive with default data path
0142     if (profilePath.isEmpty() || !QFile::exists(profilePath)) {
0143         profilePath = QDir::cleanPath(qApp->applicationDirPath() + QStringLiteral("/../Resources/%1/profiles").arg(libName));
0144         qWarning() << "profilePath from appDir/../Resources: " << profilePath;
0145     }
0146 #endif
0147     // fallback to build-time definition
0148     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && !QStringLiteral(MLT_DATADIR).isEmpty()) {
0149         profilePath = QStringLiteral(MLT_DATADIR) + QStringLiteral("/profiles");
0150         qWarning() << "profilePath from build-time MLT_DATADIR: " << profilePath;
0151     }
0152     KdenliveSettings::setMltpath(profilePath);
0153 
0154 #ifdef Q_OS_WIN
0155     QString exeSuffix = ".exe";
0156 #else
0157     QString exeSuffix = "";
0158 #endif
0159     QString meltPath;
0160     if (qEnvironmentVariableIsSet("MLT_PREFIX")) {
0161         meltPath = qgetenv("MLT_PREFIX") + QStringLiteral("/bin/%1").arg(appName) + exeSuffix;
0162         qWarning() << "meltPath from $MLT_PREFIX/bin: " << meltPath;
0163     }
0164 #ifdef Q_OS_MAC
0165     if ((meltPath.isEmpty() || !QFile::exists(meltPath)) && qEnvironmentVariableIsSet("MLT_PREFIX")) {
0166         meltPath = qgetenv("MLT_PREFIX") + QStringLiteral("/MacOS/%1").arg(appName);
0167         qWarning() << "meltPath from MLT_PREFIX/MacOS: " << meltPath;
0168     }
0169 #endif
0170 #if (!(defined(Q_OS_WIN) || defined(Q_OS_MAC)))
0171     // stored setting should not be considered on windows as MLT is distributed with each new Kdenlive version
0172     if (meltPath.isEmpty() || !QFile::exists(meltPath)) {
0173         meltPath = KdenliveSettings::meltpath();
0174         qWarning() << "meltPath from KdenliveSetting::meltPath: " << meltPath;
0175     }
0176 #endif
0177     if (meltPath.isEmpty() || !QFile::exists(meltPath)) {
0178         meltPath = QDir::cleanPath(profilePath + QStringLiteral("/../../../bin/%1").arg(appName)) + exeSuffix;
0179         qWarning() << "meltPath from profilePath/../../../bin: " << meltPath;
0180     }
0181 #ifdef Q_OS_MAC
0182     if (meltPath.isEmpty() || !QFile::exists(meltPath)) {
0183         meltPath = QDir::cleanPath(profilePath + QStringLiteral("/../../../MacOS/%1").arg(appName));
0184         qWarning() << "meltPath from profilePath/../../../MacOS: " << meltPath;
0185     }
0186 #endif
0187     if (meltPath.isEmpty() || !QFile::exists(meltPath)) {
0188         meltPath = QStandardPaths::findExecutable(appName);
0189         qWarning() << "meltPath from findExe" << appName << ": " << meltPath;
0190     }
0191     if (meltPath.isEmpty() || !QFile::exists(meltPath)) {
0192         meltPath = QStandardPaths::findExecutable("mlt-melt");
0193         qWarning() << "meltPath from findExe \"mlt-melt\" : " << meltPath;
0194     }
0195     KdenliveSettings::setMeltpath(meltPath);
0196 
0197     if (meltPath.isEmpty() && !qEnvironmentVariableIsSet("MLT_TESTS")) {
0198         // Cannot find the MLT melt renderer, ask for location
0199         QScopedPointer<KUrlRequesterDialog> getUrl(
0200             new KUrlRequesterDialog(QUrl(), i18n("Cannot find the melt program required for rendering (part of MLT)"), pCore->window()));
0201         if (getUrl->exec() == QDialog::Rejected) {
0202             ::exit(0);
0203         } else {
0204             meltPath = getUrl->selectedUrl().toLocalFile();
0205             if (meltPath.isEmpty()) {
0206                 ::exit(0);
0207             } else {
0208                 KdenliveSettings::setMeltpath(meltPath);
0209             }
0210         }
0211     }
0212     if (profilePath.isEmpty()) {
0213         profilePath = QDir::cleanPath(meltPath + QStringLiteral("/../../share/%1/profiles").arg(libName));
0214         KdenliveSettings::setMltpath(profilePath);
0215     }
0216     QStringList profilesFilter;
0217     profilesFilter << QStringLiteral("*");
0218     QStringList profilesList = QDir(profilePath).entryList(profilesFilter, QDir::Files);
0219     if (profilesList.isEmpty()) {
0220         // Cannot find MLT path, try finding melt
0221         if (!meltPath.isEmpty()) {
0222             if (meltPath.contains(QLatin1Char('/'))) {
0223                 profilePath = meltPath.section(QLatin1Char('/'), 0, -2) + QStringLiteral("/share/%1/profiles").arg(libName);
0224             } else {
0225                 profilePath = qApp->applicationDirPath() + QStringLiteral("/share/%1/profiles").arg(libName);
0226             }
0227             profilePath = QStringLiteral("/usr/local/share/%1/profiles").arg(libName);
0228             KdenliveSettings::setMltpath(profilePath);
0229             profilesList = QDir(profilePath).entryList(profilesFilter, QDir::Files);
0230         }
0231         if (profilesList.isEmpty()) {
0232             // Cannot find the MLT profiles, ask for location
0233             QScopedPointer<KUrlRequesterDialog> getUrl(
0234                 new KUrlRequesterDialog(QUrl::fromLocalFile(profilePath), i18n("Cannot find your MLT profiles, please give the path"), pCore->window()));
0235             getUrl->urlRequester()->setMode(KFile::Directory);
0236             if (getUrl->exec() == QDialog::Rejected) {
0237                 ::exit(0);
0238             } else {
0239                 profilePath = getUrl->selectedUrl().toLocalFile();
0240                 if (profilePath.isEmpty()) {
0241                     ::exit(0);
0242                 } else {
0243                     KdenliveSettings::setMltpath(profilePath);
0244                     profilesList = QDir(profilePath).entryList(profilesFilter, QDir::Files);
0245                 }
0246             }
0247         }
0248     }
0249     // Parse again MLT profiles to build a list of available video formats
0250     if (profilesList.isEmpty()) {
0251         locateMeltAndProfilesPath();
0252     }
0253 }
0254 
0255 std::unique_ptr<Mlt::Repository> &MltConnection::getMltRepository()
0256 {
0257     return m_repository;
0258 }
0259 
0260 void MltConnection::refreshLumas()
0261 {
0262     // Check for Kdenlive installed luma files, add empty string at start for no luma
0263     if (qEnvironmentVariableIsSet("MLT_TESTS")) {
0264         // No need for luma list / thumbs in tests
0265         return;
0266     }
0267     QStringList fileFilters;
0268     MainWindow::m_lumaFiles.clear();
0269     fileFilters << QStringLiteral("*.png") << QStringLiteral("*.pgm");
0270     QStringList customLumas = QStandardPaths::locateAll(QStandardPaths::AppLocalDataLocation, QStringLiteral("lumas"), QStandardPaths::LocateDirectory);
0271     customLumas.append(QString(mlt_environment("MLT_DATA")) + QStringLiteral("/lumas"));
0272     customLumas.removeDuplicates();
0273     QStringList hdLumas;
0274     QStringList sdLumas;
0275     QStringList ntscLumas;
0276     QStringList verticalLumas;
0277     QStringList squareLumas;
0278     QStringList allImagefiles;
0279     for (const QString &folder : qAsConst(customLumas)) {
0280         QDir topDir(folder);
0281         QStringList folders = topDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
0282         QString format;
0283         for (const QString &f : qAsConst(folders)) {
0284             QStringList imagefiles;
0285             QDir dir(topDir.absoluteFilePath(f));
0286             QStringList filesnames;
0287             QDirIterator it(dir.absolutePath(), fileFilters, QDir::Files, QDirIterator::Subdirectories);
0288             while (it.hasNext()) {
0289                 filesnames.append(it.next());
0290             }
0291             if (MainWindow::m_lumaFiles.contains(format)) {
0292                 imagefiles = MainWindow::m_lumaFiles.value(format);
0293             }
0294             for (const QString &fname : qAsConst(filesnames)) {
0295                 imagefiles.append(dir.absoluteFilePath(fname));
0296             }
0297             if (f == QLatin1String("HD")) {
0298                 hdLumas << imagefiles;
0299             } else if (f == QLatin1String("PAL")) {
0300                 sdLumas << imagefiles;
0301             } else if (f == QLatin1String("NTSC")) {
0302                 ntscLumas << imagefiles;
0303             } else if (f == QLatin1String("VERTICAL")) {
0304                 verticalLumas << imagefiles;
0305             } else if (f == QLatin1String("SQUARE")) {
0306                 squareLumas << imagefiles;
0307             }
0308             allImagefiles << imagefiles;
0309         }
0310     }
0311     // Insert MLT builtin lumas (created on the fly)
0312     QStringList autoLumas;
0313     for (int i = 1; i < 23; i++) {
0314         QString imageName = QStringLiteral("luma%1.pgm").arg(i, 2, 10, QLatin1Char('0'));
0315         autoLumas << imageName;
0316     }
0317     hdLumas << autoLumas;
0318     sdLumas << autoLumas;
0319     ntscLumas << autoLumas;
0320     verticalLumas << autoLumas;
0321     squareLumas << autoLumas;
0322     MainWindow::m_lumaFiles.insert(QStringLiteral("16_9"), hdLumas);
0323     MainWindow::m_lumaFiles.insert(QStringLiteral("9_16"), verticalLumas);
0324     MainWindow::m_lumaFiles.insert(QStringLiteral("square"), squareLumas);
0325     MainWindow::m_lumaFiles.insert(QStringLiteral("PAL"), sdLumas);
0326     MainWindow::m_lumaFiles.insert(QStringLiteral("NTSC"), ntscLumas);
0327     allImagefiles.removeDuplicates();
0328 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0329     QtConcurrent::run(pCore.get(), &Core::buildLumaThumbs, allImagefiles);
0330 #else
0331     QtConcurrent::run(&Core::buildLumaThumbs, pCore.get(), allImagefiles);
0332 #endif
0333 }