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