Chris@49
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@0
|
2
|
Chris@0
|
3 /*
|
Chris@52
|
4 Sonic Visualiser
|
Chris@52
|
5 An audio file viewer and annotation editor.
|
Chris@52
|
6 Centre for Digital Music, Queen Mary, University of London.
|
Chris@1241
|
7 This file copyright 2006-2016 Chris Cannam and QMUL.
|
Chris@0
|
8
|
Chris@52
|
9 This program is free software; you can redistribute it and/or
|
Chris@52
|
10 modify it under the terms of the GNU General Public License as
|
Chris@52
|
11 published by the Free Software Foundation; either version 2 of the
|
Chris@52
|
12 License, or (at your option) any later version. See the file
|
Chris@52
|
13 COPYING included with this distribution for more information.
|
Chris@0
|
14 */
|
Chris@0
|
15
|
Chris@1249
|
16 #ifdef HAVE_PIPER
|
Chris@1249
|
17
|
Chris@1225
|
18 #include "PiperVampPluginFactory.h"
|
Chris@0
|
19 #include "PluginIdentifier.h"
|
Chris@0
|
20
|
Chris@150
|
21 #include "system/System.h"
|
Chris@66
|
22
|
Chris@1179
|
23 #include "PluginScan.h"
|
Chris@1179
|
24
|
Chris@1224
|
25 #ifdef _WIN32
|
Chris@1224
|
26 #undef VOID
|
Chris@1224
|
27 #undef ERROR
|
Chris@1224
|
28 #define CAPNP_LITE 1
|
Chris@1224
|
29 #endif
|
Chris@1225
|
30
|
Chris@1378
|
31 #include "vamp-client/qt/PiperAutoPlugin.h"
|
Chris@1370
|
32 #include "vamp-client/qt/ProcessQtTransport.h"
|
Chris@1370
|
33 #include "vamp-client/CapnpRRClient.h"
|
Chris@1210
|
34
|
Chris@66
|
35 #include <QDir>
|
Chris@66
|
36 #include <QFile>
|
Chris@66
|
37 #include <QFileInfo>
|
Chris@165
|
38 #include <QTextStream>
|
Chris@1227
|
39 #include <QCoreApplication>
|
Chris@66
|
40
|
Chris@0
|
41 #include <iostream>
|
Chris@0
|
42
|
Chris@408
|
43 #include "base/Profiler.h"
|
Chris@1241
|
44 #include "base/HelperExecPath.h"
|
Chris@408
|
45
|
Chris@1164
|
46 using namespace std;
|
Chris@1164
|
47
|
Chris@249
|
48 //#define DEBUG_PLUGIN_SCAN_AND_INSTANTIATE 1
|
Chris@249
|
49
|
Chris@1264
|
50 class PiperVampPluginFactory::Logger : public piper_vamp::client::LogCallback {
|
Chris@1264
|
51 protected:
|
Chris@1264
|
52 void log(std::string message) const override {
|
Chris@1264
|
53 SVDEBUG << "PiperVampPluginFactory: " << message << endl;
|
Chris@1264
|
54 }
|
Chris@1264
|
55 };
|
Chris@1264
|
56
|
Chris@1264
|
57 PiperVampPluginFactory::PiperVampPluginFactory() :
|
Chris@1264
|
58 m_logger(new Logger)
|
Chris@66
|
59 {
|
Chris@1241
|
60 QString serverName = "piper-vamp-simple-server";
|
Chris@1559
|
61 float minimumVersion = 2.0;
|
Chris@1240
|
62
|
Chris@1246
|
63 HelperExecPath hep(HelperExecPath::AllInstalled);
|
Chris@1240
|
64
|
Chris@1559
|
65 auto servers = hep.getHelperExecutables(serverName);
|
Chris@1559
|
66
|
Chris@1559
|
67 for (auto n: servers) {
|
Chris@1247
|
68 SVDEBUG << "NOTE: PiperVampPluginFactory: Found server: "
|
Chris@1247
|
69 << n.executable << endl;
|
Chris@1559
|
70 if (serverMeetsMinimumVersion(n, minimumVersion)) {
|
Chris@1559
|
71 m_servers.push_back(n);
|
Chris@1559
|
72 } else {
|
Chris@1559
|
73 SVCERR << "WARNING: PiperVampPluginFactory: Server at "
|
Chris@1559
|
74 << n.executable
|
Chris@1559
|
75 << " does not meet minimum version requirement (version >= "
|
Chris@1559
|
76 << minimumVersion << ")" << endl;
|
Chris@1559
|
77 }
|
Chris@1246
|
78 }
|
Chris@1246
|
79
|
Chris@1240
|
80 if (m_servers.empty()) {
|
Chris@1247
|
81 SVDEBUG << "NOTE: No Piper Vamp servers found in installation;"
|
Chris@1559
|
82 << " the following paths are either absent or fail "
|
Chris@1559
|
83 << "minimum-version check:" << endl;
|
Chris@1246
|
84 for (auto d: hep.getHelperCandidatePaths(serverName)) {
|
Chris@1247
|
85 SVDEBUG << "NOTE: " << d << endl;
|
Chris@1241
|
86 }
|
Chris@1240
|
87 }
|
Chris@1240
|
88 }
|
Chris@1240
|
89
|
Chris@1264
|
90 PiperVampPluginFactory::~PiperVampPluginFactory()
|
Chris@1264
|
91 {
|
Chris@1264
|
92 delete m_logger;
|
Chris@1264
|
93 }
|
Chris@1264
|
94
|
Chris@1559
|
95 bool
|
Chris@1559
|
96 PiperVampPluginFactory::serverMeetsMinimumVersion(const HelperExecPath::HelperExec &server,
|
Chris@1559
|
97 float minimumVersion)
|
Chris@1559
|
98 {
|
Chris@1559
|
99 QProcess process;
|
Chris@1559
|
100 QString executable = server.executable;
|
Chris@1559
|
101 process.setReadChannel(QProcess::StandardOutput);
|
Chris@1559
|
102 process.setProcessChannelMode(QProcess::ForwardedErrorChannel);
|
Chris@1559
|
103 process.start(executable, { "--version" });
|
Chris@1559
|
104
|
Chris@1559
|
105 if (!process.waitForStarted()) {
|
Chris@1559
|
106 QProcess::ProcessError err = process.error();
|
Chris@1559
|
107 if (err == QProcess::FailedToStart) {
|
Chris@1559
|
108 SVCERR << "WARNING: Unable to start server " << executable
|
Chris@1559
|
109 << " for version check" << endl;
|
Chris@1559
|
110 } else if (err == QProcess::Crashed) {
|
Chris@1559
|
111 SVCERR << "WARNING: Server " << executable
|
Chris@1559
|
112 << " crashed on version check" << endl;
|
Chris@1559
|
113 } else {
|
Chris@1559
|
114 SVCERR << "WARNING: Server " << executable
|
Chris@1559
|
115 << " failed on version check with error code "
|
Chris@1559
|
116 << err << endl;
|
Chris@1559
|
117 }
|
Chris@1559
|
118 return false;
|
Chris@1559
|
119 }
|
Chris@1559
|
120 process.waitForFinished();
|
Chris@1559
|
121
|
Chris@1559
|
122 QByteArray output = process.readAllStandardOutput();
|
Chris@1559
|
123 while (output.endsWith('\n') || output.endsWith('\r')) {
|
Chris@1559
|
124 output.chop(1);
|
Chris@1559
|
125 }
|
Chris@1559
|
126
|
Chris@1559
|
127 QString outputString(output);
|
Chris@1559
|
128 bool ok = false;
|
Chris@1559
|
129 float version = outputString.toFloat(&ok);
|
Chris@1559
|
130 if (!ok) {
|
Chris@1559
|
131 SVCERR << "WARNING: Failed to convert server version response \""
|
Chris@1559
|
132 << outputString << "\" into one- or two-part version number"
|
Chris@1559
|
133 << endl;
|
Chris@1559
|
134 }
|
Chris@1559
|
135
|
Chris@1559
|
136 SVDEBUG << "Server " << executable << " reports version number "
|
Chris@1559
|
137 << version << endl;
|
Chris@1559
|
138
|
Chris@1568
|
139 float eps = 1e-6f;
|
Chris@1559
|
140 return (version >= minimumVersion ||
|
Chris@1559
|
141 fabsf(version - minimumVersion) < eps); // arf
|
Chris@1559
|
142 }
|
Chris@1559
|
143
|
Chris@1164
|
144 vector<QString>
|
Chris@1227
|
145 PiperVampPluginFactory::getPluginIdentifiers(QString &errorMessage)
|
Chris@0
|
146 {
|
Chris@1225
|
147 Profiler profiler("PiperVampPluginFactory::getPluginIdentifiers");
|
Chris@408
|
148
|
Chris@1209
|
149 QMutexLocker locker(&m_mutex);
|
Chris@1209
|
150
|
Chris@1240
|
151 if (m_servers.empty()) {
|
Chris@1227
|
152 errorMessage = QObject::tr("External plugin host executable does not appear to be installed");
|
Chris@1227
|
153 return {};
|
Chris@1227
|
154 }
|
Chris@1227
|
155
|
Chris@1209
|
156 if (m_pluginData.empty()) {
|
Chris@1227
|
157 populate(errorMessage);
|
Chris@1209
|
158 }
|
Chris@1209
|
159
|
Chris@1164
|
160 vector<QString> rv;
|
Chris@1179
|
161
|
Chris@1209
|
162 for (const auto &d: m_pluginData) {
|
Chris@1225
|
163 rv.push_back(QString("vamp:") + QString::fromStdString(d.second.pluginKey));
|
Chris@66
|
164 }
|
Chris@66
|
165
|
Chris@0
|
166 return rv;
|
Chris@0
|
167 }
|
Chris@0
|
168
|
Chris@66
|
169 Vamp::Plugin *
|
Chris@1225
|
170 PiperVampPluginFactory::instantiatePlugin(QString identifier,
|
Chris@1225
|
171 sv_samplerate_t inputSampleRate)
|
Chris@0
|
172 {
|
Chris@1225
|
173 Profiler profiler("PiperVampPluginFactory::instantiatePlugin");
|
Chris@1225
|
174
|
Chris@1240
|
175 if (m_origins.find(identifier) == m_origins.end()) {
|
Chris@1428
|
176 SVCERR << "ERROR: No known server for identifier " << identifier << endl;
|
Chris@1240
|
177 return 0;
|
Chris@1240
|
178 }
|
Chris@1240
|
179
|
Chris@1225
|
180 auto psd = getPluginStaticData(identifier);
|
Chris@1225
|
181 if (psd.pluginKey == "") {
|
Chris@1225
|
182 return 0;
|
Chris@1225
|
183 }
|
Chris@1264
|
184
|
Chris@1378
|
185 SVDEBUG << "PiperVampPluginFactory: Creating PiperAutoPlugin for server "
|
Chris@1264
|
186 << m_origins[identifier] << ", identifier " << identifier << endl;
|
Chris@1210
|
187
|
Chris@1378
|
188 auto ap = new piper_vamp::client::PiperAutoPlugin
|
Chris@1240
|
189 (m_origins[identifier].toStdString(),
|
Chris@1264
|
190 psd.pluginKey,
|
Chris@1264
|
191 float(inputSampleRate),
|
Chris@1264
|
192 0,
|
Chris@1264
|
193 m_logger);
|
Chris@1240
|
194
|
Chris@1210
|
195 if (!ap->isOK()) {
|
Chris@1210
|
196 delete ap;
|
Chris@1210
|
197 return 0;
|
Chris@1225
|
198 }
|
Chris@1225
|
199
|
Chris@1225
|
200 return ap;
|
Chris@1225
|
201 }
|
Chris@1225
|
202
|
Chris@1225
|
203 piper_vamp::PluginStaticData
|
Chris@1225
|
204 PiperVampPluginFactory::getPluginStaticData(QString identifier)
|
Chris@1225
|
205 {
|
Chris@1225
|
206 if (m_pluginData.find(identifier) != m_pluginData.end()) {
|
Chris@1225
|
207 return m_pluginData[identifier];
|
Chris@1210
|
208 } else {
|
Chris@1225
|
209 return {};
|
Chris@1210
|
210 }
|
Chris@298
|
211 }
|
Chris@298
|
212
|
Chris@165
|
213 QString
|
Chris@1225
|
214 PiperVampPluginFactory::getPluginCategory(QString identifier)
|
Chris@165
|
215 {
|
Chris@1223
|
216 if (m_taxonomy.find(identifier) != m_taxonomy.end()) {
|
Chris@1223
|
217 return m_taxonomy[identifier];
|
Chris@1223
|
218 } else {
|
Chris@1223
|
219 return {};
|
Chris@1223
|
220 }
|
Chris@165
|
221 }
|
Chris@165
|
222
|
Chris@1464
|
223 QString
|
Chris@1464
|
224 PiperVampPluginFactory::getPluginLibraryPath(QString identifier)
|
Chris@1464
|
225 {
|
Chris@1464
|
226 // What we want to return here is the file path of the library in
|
Chris@1464
|
227 // which the plugin was actually found -- we want to be paranoid
|
Chris@1464
|
228 // about that and not just query
|
Chris@1464
|
229 // Vamp::HostExt::PluginLoader::getLibraryPathForPlugin to return
|
Chris@1464
|
230 // what the SDK thinks the likely location would be (in case our
|
Chris@1464
|
231 // search order turns out to have been different)
|
Chris@1464
|
232
|
Chris@1464
|
233 QStringList bits = identifier.split(':');
|
Chris@1464
|
234 if (bits.size() > 1) {
|
Chris@1464
|
235 QString soname = bits[bits.size() - 2];
|
Chris@1464
|
236 auto i = m_libraries.find(soname);
|
Chris@1464
|
237 if (i != m_libraries.end()) {
|
Chris@1464
|
238 return i->second;
|
Chris@1464
|
239 }
|
Chris@1464
|
240 }
|
Chris@1464
|
241 return QString();
|
Chris@1464
|
242 }
|
Chris@1464
|
243
|
Chris@165
|
244 void
|
Chris@1227
|
245 PiperVampPluginFactory::populate(QString &errorMessage)
|
Chris@165
|
246 {
|
Chris@1240
|
247 QString someError;
|
Chris@1227
|
248
|
Chris@1246
|
249 for (auto s: m_servers) {
|
Chris@1240
|
250
|
Chris@1240
|
251 populateFrom(s, someError);
|
Chris@1240
|
252
|
Chris@1240
|
253 if (someError != "" && errorMessage == "") {
|
Chris@1240
|
254 errorMessage = someError;
|
Chris@1240
|
255 }
|
Chris@1240
|
256 }
|
Chris@1240
|
257 }
|
Chris@1240
|
258
|
Chris@1240
|
259 void
|
Chris@1246
|
260 PiperVampPluginFactory::populateFrom(const HelperExecPath::HelperExec &server,
|
Chris@1246
|
261 QString &errorMessage)
|
Chris@1240
|
262 {
|
Chris@1246
|
263 QString tag = server.tag;
|
Chris@1246
|
264 string executable = server.executable.toStdString();
|
Chris@1246
|
265
|
Chris@1246
|
266 PluginScan *scan = PluginScan::getInstance();
|
Chris@1246
|
267 auto candidateLibraries =
|
Chris@1246
|
268 scan->getCandidateLibrariesFor(PluginScan::VampPlugin);
|
Chris@1246
|
269
|
Chris@1264
|
270 SVDEBUG << "PiperVampPluginFactory: Populating from " << executable << endl;
|
Chris@1250
|
271 SVDEBUG << "INFO: Have " << candidateLibraries.size()
|
Chris@1264
|
272 << " candidate Vamp plugin libraries from scanner" << endl;
|
Chris@1249
|
273
|
Chris@1246
|
274 vector<string> from;
|
Chris@1246
|
275 for (const auto &c: candidateLibraries) {
|
Chris@1246
|
276 if (c.helperTag == tag) {
|
Chris@1246
|
277 string soname = QFileInfo(c.libraryPath).baseName().toStdString();
|
Chris@1247
|
278 SVDEBUG << "INFO: For tag \"" << tag << "\" giving library " << soname << endl;
|
Chris@1246
|
279 from.push_back(soname);
|
Chris@1482
|
280 QString qsoname = QString::fromStdString(soname);
|
Chris@1482
|
281 if (m_libraries.find(qsoname) == m_libraries.end()) {
|
Chris@1482
|
282 m_libraries[qsoname] = c.libraryPath;
|
Chris@1482
|
283 }
|
Chris@1246
|
284 }
|
Chris@1246
|
285 }
|
Chris@1246
|
286
|
Chris@1246
|
287 if (from.empty()) {
|
Chris@1247
|
288 SVDEBUG << "PiperVampPluginFactory: No candidate libraries for tag \""
|
Chris@1246
|
289 << tag << "\"";
|
Chris@1246
|
290 if (scan->scanSucceeded()) {
|
Chris@1246
|
291 // we have to assume that they all failed to load (i.e. we
|
Chris@1246
|
292 // exclude them all) rather than sending an empty list
|
Chris@1246
|
293 // (which would mean no exclusions)
|
Chris@1247
|
294 SVDEBUG << ", skipping" << endl;
|
Chris@1246
|
295 return;
|
Chris@1246
|
296 } else {
|
Chris@1247
|
297 SVDEBUG << ", but it seems the scan failed, so bumbling on anyway" << endl;
|
Chris@1246
|
298 }
|
Chris@1246
|
299 }
|
Chris@1246
|
300
|
Chris@1264
|
301 piper_vamp::client::ProcessQtTransport transport(executable, "capnp", m_logger);
|
Chris@1227
|
302 if (!transport.isOK()) {
|
Chris@1264
|
303 SVDEBUG << "PiperVampPluginFactory: Failed to start Piper process transport" << endl;
|
Chris@1227
|
304 errorMessage = QObject::tr("Could not start external plugin host");
|
Chris@1227
|
305 return;
|
Chris@1227
|
306 }
|
Chris@1234
|
307
|
Chris@1264
|
308 piper_vamp::client::CapnpRRClient client(&transport, m_logger);
|
Chris@1248
|
309
|
Chris@1248
|
310 piper_vamp::ListRequest req;
|
Chris@1248
|
311 req.from = from;
|
Chris@1248
|
312
|
Chris@1248
|
313 piper_vamp::ListResponse resp;
|
Chris@1234
|
314
|
Chris@1234
|
315 try {
|
Chris@1378
|
316 resp = client.list(req);
|
Chris@1465
|
317 } catch (const piper_vamp::client::ServerCrashed &) {
|
Chris@1264
|
318 SVDEBUG << "PiperVampPluginFactory: Piper server crashed" << endl;
|
Chris@1234
|
319 errorMessage = QObject::tr
|
Chris@1234
|
320 ("External plugin host exited unexpectedly while listing plugins");
|
Chris@1234
|
321 return;
|
Chris@1235
|
322 } catch (const std::exception &e) {
|
Chris@1264
|
323 SVDEBUG << "PiperVampPluginFactory: Exception caught: " << e.what() << endl;
|
Chris@1235
|
324 errorMessage = QObject::tr("External plugin host invocation failed: %1")
|
Chris@1235
|
325 .arg(e.what());
|
Chris@1235
|
326 return;
|
Chris@1234
|
327 }
|
Chris@1213
|
328
|
Chris@1247
|
329 SVDEBUG << "PiperVampPluginFactory: server \"" << executable << "\" lists "
|
Chris@1248
|
330 << resp.available.size() << " plugin(s)" << endl;
|
Chris@1244
|
331
|
Chris@1248
|
332 for (const auto &pd: resp.available) {
|
Chris@1240
|
333
|
Chris@1213
|
334 QString identifier =
|
Chris@1213
|
335 QString("vamp:") + QString::fromStdString(pd.pluginKey);
|
Chris@1213
|
336
|
Chris@1240
|
337 if (m_origins.find(identifier) != m_origins.end()) {
|
Chris@1240
|
338 // have it already, from a higher-priority server
|
Chris@1240
|
339 // (e.g. 64-bit instead of 32-bit)
|
Chris@1240
|
340 continue;
|
Chris@1240
|
341 }
|
Chris@1240
|
342
|
Chris@1246
|
343 m_origins[identifier] = server.executable;
|
Chris@1240
|
344
|
Chris@1225
|
345 m_pluginData[identifier] = pd;
|
Chris@1225
|
346
|
Chris@1213
|
347 QStringList catlist;
|
Chris@1213
|
348 for (const auto &cs: pd.category) {
|
Chris@1213
|
349 catlist.push_back(QString::fromStdString(cs));
|
Chris@1213
|
350 }
|
Chris@1223
|
351
|
Chris@1213
|
352 m_taxonomy[identifier] = catlist.join(" > ");
|
Chris@1213
|
353 }
|
Chris@1209
|
354 }
|
Chris@165
|
355
|
Chris@1249
|
356 #endif
|