annotate plugin/PiperVampPluginFactory.cpp @ 1769:9a8327e7b2dc

Respect use-flexi-note-model setting
author Chris Cannam
date Thu, 18 Jul 2019 14:42:24 +0100
parents 70e172e6cc59
children 5f8fbbde08ff
rev   line source
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@1582 177 return nullptr;
Chris@1240 178 }
Chris@1240 179
Chris@1225 180 auto psd = getPluginStaticData(identifier);
Chris@1225 181 if (psd.pluginKey == "") {
Chris@1582 182 return nullptr;
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@1582 197 return nullptr;
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