1 /*
2 SPDX-FileCopyrightText: 2007 Jean-Baptiste Mardelle <jb@kdenlive.org>
3 SPDX-FileCopyrightText: 2014 Till Theato <root@ttill.de>
4 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5 */
6 
7 #include "mltconnection.h"
8 #include "core.h"
9 #include "kdenlivesettings.h"
10 #include "mainwindow.h"
11 #include "mlt_config.h"
12 #include <KUrlRequester>
13 #include <KUrlRequesterDialog>
14 #include <klocalizedstring.h>
15 #include <QtConcurrent>
16 
17 #include <clocale>
18 #include <lib/localeHandling.h>
19 #include <mlt++/MltFactory.h>
20 #include <mlt++/MltRepository.h>
21 
mlt_log_handler(void * service,int mlt_level,const char * format,va_list args)22 static void mlt_log_handler(void *service, int mlt_level, const char *format, va_list args)
23 {
24     if (mlt_level > mlt_log_get_level())
25         return;
26     QString message;
27     mlt_properties properties = service? MLT_SERVICE_PROPERTIES(mlt_service(service)) : nullptr;
28     if (properties) {
29         char *mlt_type = mlt_properties_get(properties, "mlt_type");
30         char *service_name = mlt_properties_get(properties, "mlt_service");
31         char *resource = mlt_properties_get(properties, "resource");
32         char *id = mlt_properties_get(properties, "id");
33         if (!resource || resource[0] != '<' || resource[strlen(resource) - 1] != '>')
34             mlt_type = mlt_properties_get(properties, "mlt_type" );
35         if (service_name)
36             message = QString("[%1 %2 %3] ").arg(mlt_type, service_name, id);
37         else
38             message = QString::asprintf("[%s %p] ", mlt_type, service);
39         if (resource)
40             message.append(QString("\"%1\" ").arg(resource));
41         message.append(QString::vasprintf(format, args));
42         message.replace('\n', "");
43         if (!strcmp(mlt_type, "filter")) {
44             pCore->processInvalidFilter(service_name, id, message);
45         }
46     } else {
47         message = QString::vasprintf(format, args);
48         message.replace('\n', "");
49     }
50     qDebug() << "MLT:" << message;
51 }
52 
53 
54 std::unique_ptr<MltConnection> MltConnection::m_self;
MltConnection(const QString & mltPath)55 MltConnection::MltConnection(const QString &mltPath)
56 {
57     // Disable VDPAU that crashes in multithread environment.
58     // TODO: make configurable
59     setenv("MLT_NO_VDPAU", "1", 1);
60 
61     // After initialising the MLT factory, set the locale back from user default to C
62     // to ensure numbers are always serialised with . as decimal point.
63     m_repository = std::unique_ptr<Mlt::Repository>(Mlt::Factory::init());
64 
65 #ifdef Q_OS_FREEBSD
66     setlocale(MLT_LC_CATEGORY, nullptr);
67 #else
68     std::setlocale(MLT_LC_CATEGORY, nullptr);
69 #endif
70 
71     locateMeltAndProfilesPath(mltPath);
72 
73     // Retrieve the list of available producers.
74     QScopedPointer<Mlt::Properties> producers(m_repository->producers());
75     QStringList producersList;
76     int nb_producers = producers->count();
77     producersList.reserve(nb_producers);
78     for (int i = 0; i < nb_producers; ++i) {
79         producersList << producers->get_name(i);
80     }
81     KdenliveSettings::setProducerslist(producersList);
82     mlt_log_set_level(MLT_LOG_WARNING);
83     mlt_log_set_callback(mlt_log_handler);
84     refreshLumas();
85 }
86 
construct(const QString & mltPath)87 void MltConnection::construct(const QString &mltPath)
88 {
89     if (MltConnection::m_self) {
90         qWarning() << "Trying to open a 2nd mlt connection";
91         return;
92     }
93     MltConnection::m_self.reset(new MltConnection(mltPath));
94 }
95 
self()96 std::unique_ptr<MltConnection> &MltConnection::self()
97 {
98     return MltConnection::m_self;
99 }
100 
locateMeltAndProfilesPath(const QString & mltPath)101 void MltConnection::locateMeltAndProfilesPath(const QString &mltPath)
102 {
103     QString profilePath = mltPath;
104     QString appName;
105     QString libName;
106 #if(defined(Q_OS_WIN)||defined(Q_OS_MAC))
107     appName = QStringLiteral("melt");
108     libName = QStringLiteral("mlt");
109 #else
110     appName = QStringLiteral("melt-7");
111     libName = QStringLiteral("mlt-7");
112 #endif
113     // environment variables should override other settings
114     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && qEnvironmentVariableIsSet("MLT_PROFILES_PATH")) {
115         profilePath = qgetenv("MLT_PROFILES_PATH");
116         qWarning() << "profilePath from $MLT_PROFILES_PATH: " << profilePath;
117     }
118     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && qEnvironmentVariableIsSet("MLT_DATA")) {
119         profilePath = qgetenv("MLT_DATA") + QStringLiteral("/profiles");
120         qWarning() << "profilePath from $MLT_DATA: " << profilePath;
121     }
122     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && qEnvironmentVariableIsSet("MLT_PREFIX")) {
123         profilePath = qgetenv("MLT_PREFIX") + QStringLiteral("/share/%1/profiles").arg(libName);
124         qWarning() << "profilePath from $MLT_PREFIX/share: " << profilePath;
125     }
126 #ifdef Q_OS_MAC
127     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && qEnvironmentVariableIsSet("MLT_PREFIX")) {
128         profilePath = qgetenv("MLT_PREFIX") + QStringLiteral("/Resources/%1/profiles").arg(libName);
129         qWarning() << "profilePath from $MLT_PREFIX/Resources: " << profilePath;
130     }
131 #endif
132 #if(!(defined(Q_OS_WIN)||defined(Q_OS_MAC)))
133     // stored setting should not be considered on windows as MLT is distributed with each new Kdenlive version
134     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && !KdenliveSettings::mltpath().isEmpty()) {
135         profilePath = KdenliveSettings::mltpath();
136         qWarning() << "profilePath from KdenliveSetting::mltPath: " << profilePath;
137     }
138 #endif
139     // try to automatically guess MLT path if installed with the same prefix as kdenlive with default data path
140     if (profilePath.isEmpty() || !QFile::exists(profilePath)) {
141         profilePath = QDir::cleanPath(qApp->applicationDirPath() + QStringLiteral("/../share/%1/profiles").arg(libName));
142         qWarning() << "profilePath from appDir/../share: " << profilePath;
143     }
144 #ifdef Q_OS_MAC
145     // try to automatically guess MLT path if installed with the same prefix as kdenlive with default data path
146     if (profilePath.isEmpty() || !QFile::exists(profilePath)) {
147         profilePath = QDir::cleanPath(qApp->applicationDirPath() + QStringLiteral("/../Resources/%1/profiles").arg(libName));
148         qWarning() << "profilePath from appDir/../Resources: " << profilePath;
149     }
150 #endif
151     // fallback to build-time definition
152     if ((profilePath.isEmpty() || !QFile::exists(profilePath)) && !QStringLiteral(MLT_DATADIR).isEmpty()) {
153         profilePath = QStringLiteral(MLT_DATADIR) + QStringLiteral("/profiles");
154         qWarning() << "profilePath from build-time MLT_DATADIR: " << profilePath;
155     }
156     KdenliveSettings::setMltpath(profilePath);
157 
158 #ifdef Q_OS_WIN
159     QString exeSuffix = ".exe";
160 #else
161     QString exeSuffix = "";
162 #endif
163     QString meltPath;
164     if (qEnvironmentVariableIsSet("MLT_PREFIX")) {
165         meltPath = qgetenv("MLT_PREFIX") + QStringLiteral("/bin/%1").arg(appName) + exeSuffix;
166         qWarning() << "meltPath from $MLT_PREFIX/bin: " << meltPath;
167     }
168 #ifdef Q_OS_MAC
169     if ((meltPath.isEmpty() || !QFile::exists(meltPath)) && qEnvironmentVariableIsSet("MLT_PREFIX")) {
170         meltPath = qgetenv("MLT_PREFIX") + QStringLiteral("/MacOS/%1").arg(appName);
171         qWarning() << "meltPath from MLT_PREFIX/MacOS: " << meltPath;
172     }
173 #endif
174 #if(!(defined(Q_OS_WIN)||defined(Q_OS_MAC)))
175     // stored setting should not be considered on windows as MLT is distributed with each new Kdenlive version
176     if ((meltPath.isEmpty() || !QFile::exists(meltPath))) {
177         meltPath = KdenliveSettings::rendererpath();
178         qWarning() << "meltPath from KdenliveSetting::rendererPath: " << profilePath;
179     }
180 #endif
181     if ((meltPath.isEmpty() || !QFile::exists(meltPath))) {
182         meltPath = QDir::cleanPath(profilePath + QStringLiteral("/../../../bin/%1").arg(appName)) + exeSuffix;
183         qWarning() << "meltPath from profilePath/.../bin: " << profilePath;
184     }
185 #ifdef Q_OS_MAC
186     if ((meltPath.isEmpty() || !QFile::exists(meltPath))) {
187         meltPath = QDir::cleanPath(profilePath + QStringLiteral("/../../../MacOS/%1").arg(appName));
188         qWarning() << "meltPath from profilePath/.../MacOS: " << profilePath;
189     }
190 #endif
191     if ((meltPath.isEmpty() || !QFile::exists(meltPath))) {
192         meltPath = QStandardPaths::findExecutable(appName);
193         qWarning() << "meltPath from findExe: " << profilePath;
194     }
195     if ((meltPath.isEmpty() || !QFile::exists(meltPath))) {
196         meltPath = QStandardPaths::findExecutable("mlt-melt");
197         qWarning() << "meltPath from findExe: " << profilePath;
198     }
199     KdenliveSettings::setRendererpath(meltPath);
200 
201     if (meltPath.isEmpty() && !qEnvironmentVariableIsSet("MLT_TESTS")) {
202         // Cannot find the MLT melt renderer, ask for location
203         QScopedPointer<KUrlRequesterDialog> getUrl(
204             new KUrlRequesterDialog(QUrl(), i18n("Cannot find the melt program required for rendering (part of MLT)"), pCore->window()));
205         if (getUrl->exec() == QDialog::Rejected) {
206             ::exit(0);
207         } else {
208             meltPath = getUrl->selectedUrl().toLocalFile();
209             if (meltPath.isEmpty()) {
210                 ::exit(0);
211             } else {
212                 KdenliveSettings::setRendererpath(meltPath);
213             }
214         }
215     }
216     if (profilePath.isEmpty()) {
217         profilePath = QDir::cleanPath(meltPath + QStringLiteral("/../../share/%1/profiles").arg(libName));
218         KdenliveSettings::setMltpath(profilePath);
219     }
220     QStringList profilesFilter;
221     profilesFilter << QStringLiteral("*");
222     QStringList profilesList = QDir(profilePath).entryList(profilesFilter, QDir::Files);
223     if (profilesList.isEmpty()) {
224         // Cannot find MLT path, try finding melt
225         if (!meltPath.isEmpty()) {
226             if (meltPath.contains(QLatin1Char('/'))) {
227                 profilePath = meltPath.section(QLatin1Char('/'), 0, -2) + QStringLiteral("/share/%1/profiles").arg(libName);
228             } else {
229                 profilePath = qApp->applicationDirPath() + QStringLiteral("/share/%1/profiles").arg(libName);
230             }
231             profilePath = QStringLiteral("/usr/local/share/%1/profiles").arg(libName);
232             KdenliveSettings::setMltpath(profilePath);
233             profilesList = QDir(profilePath).entryList(profilesFilter, QDir::Files);
234         }
235         if (profilesList.isEmpty()) {
236             // Cannot find the MLT profiles, ask for location
237             QScopedPointer<KUrlRequesterDialog> getUrl(
238                 new KUrlRequesterDialog(QUrl::fromLocalFile(profilePath), i18n("Cannot find your MLT profiles, please give the path"), pCore->window()));
239             getUrl->urlRequester()->setMode(KFile::Directory);
240             if (getUrl->exec() == QDialog::Rejected) {
241                 ::exit(0);
242             } else {
243                 profilePath = getUrl->selectedUrl().toLocalFile();
244                 if (profilePath.isEmpty()) {
245                     ::exit(0);
246                 } else {
247                     KdenliveSettings::setMltpath(profilePath);
248                     profilesList = QDir(profilePath).entryList(profilesFilter, QDir::Files);
249                 }
250             }
251         }
252     }
253     // Parse again MLT profiles to build a list of available video formats
254     if (profilesList.isEmpty()) {
255         locateMeltAndProfilesPath();
256     }
257 }
258 
getMltRepository()259 std::unique_ptr<Mlt::Repository> &MltConnection::getMltRepository()
260 {
261     return m_repository;
262 }
263 
refreshLumas()264 void MltConnection::refreshLumas()
265 {
266     // Check for Kdenlive installed luma files, add empty string at start for no luma
267     QStringList fileFilters;
268     MainWindow::m_lumaFiles.clear();
269     fileFilters << QStringLiteral("*.png") << QStringLiteral("*.pgm");
270     QStringList customLumas = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("lumas"), QStandardPaths::LocateDirectory);
271 #ifdef Q_OS_WIN
272     // Windows: downloaded lumas are saved in AppLocalDataLocation
273     customLumas.append(QStandardPaths::locateAll(QStandardPaths::AppLocalDataLocation, QStringLiteral("lumas"), QStandardPaths::LocateDirectory));
274 #endif
275     customLumas.append(QString(mlt_environment("MLT_DATA")) + QStringLiteral("/lumas"));
276     customLumas.removeDuplicates();
277     QStringList hdLumas;
278     QStringList sdLumas;
279     QStringList allImagefiles;
280     for (const QString &folder : qAsConst(customLumas)) {
281         QDir topDir(folder);
282         QStringList folders = topDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot);
283         QString format;
284         for (const QString &f : qAsConst(folders)) {
285             QStringList imagefiles;
286             QDir dir(topDir.absoluteFilePath(f));
287             QStringList filesnames;
288             QDirIterator it(dir.absolutePath(), fileFilters, QDir::Files, QDirIterator::Subdirectories);
289             while (it.hasNext()) {
290                 filesnames.append(it.next());
291             }
292             if (MainWindow::m_lumaFiles.contains(format)) {
293                 imagefiles = MainWindow::m_lumaFiles.value(format);
294             }
295             for (const QString &fname : qAsConst(filesnames)) {
296                 imagefiles.append(dir.absoluteFilePath(fname));
297             }
298             if (f == QLatin1String("HD")) {
299                 hdLumas << imagefiles;
300             } else {
301                 sdLumas << imagefiles;
302             }
303             allImagefiles << imagefiles;
304         }
305     }
306     // Insert MLT builtin lumas (created on the fly)
307     for (int i = 1; i < 23; i++) {
308         QString imageName = QStringLiteral("luma%1.pgm").arg(i, 2, 10, QLatin1Char('0'));
309         hdLumas << imageName;
310     }
311     MainWindow::m_lumaFiles.insert(QStringLiteral("16_9"), hdLumas);
312     MainWindow::m_lumaFiles.insert(QStringLiteral("PAL"), sdLumas);
313     allImagefiles.removeDuplicates();
314     QtConcurrent::run(pCore.get(), &Core::buildLumaThumbs, allImagefiles);
315 }
316