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 }