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