annotate vampy-main.cpp @ 63:577b251cad2f

applied patch to fix bug related to numpy version string parsing
author gyorgyf
date Sat, 05 Oct 2013 13:36:27 +0100
parents b2c4c752c3bc
children 5664fe298af2
rev   line source
fazekasgy@37 1 /*
fazekasgy@37 2
fazekasgy@37 3 * Vampy : This plugin is a wrapper around the Vamp plugin API.
fazekasgy@37 4 * It allows for writing Vamp plugins in Python.
fazekasgy@37 5
fazekasgy@37 6 * Centre for Digital Music, Queen Mary University of London.
fazekasgy@37 7 * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources
fazekasgy@37 8 * for licence information.)
fazekasgy@37 9
fazekasgy@37 10 */
fazekasgy@37 11
fazekasgy@37 12 #include <Python.h>
fazekasgy@37 13
fazekasgy@37 14 #ifdef HAVE_NUMPY
fazekasgy@51 15
fazekasgy@51 16 // define a unique API pointer
fazekasgy@51 17 #define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API
fazekasgy@37 18 #include "numpy/arrayobject.h"
fazekasgy@51 19
fazekasgy@51 20 // prevent building with very old versions of numpy
fazekasgy@51 21 #ifndef NPY_VERSION
fazekasgy@51 22 #undef HAVE_NUMPY
fazekasgy@51 23 #endif
fazekasgy@51 24
fazekasgy@51 25 #endif
fazekasgy@51 26
fazekasgy@51 27 // this is not part of the API, but we will require it for a bug workaround
fazekasgy@51 28 // define this symbol if you use another version of numpy in the makefile
fazekasgy@51 29 // Vampy will not attempt to load a lower version than specified
fazekasgy@51 30 #ifdef HAVE_NUMPY
fazekasgy@51 31 #ifndef NUMPY_SHORTVERSION
fazekasgy@51 32 #define NUMPY_SHORTVERSION 1.1
fazekasgy@51 33 #endif
fazekasgy@37 34 #endif
fazekasgy@37 35
fazekasgy@37 36 #include "vamp/vamp.h"
fazekasgy@37 37 #include "vamp-sdk/PluginAdapter.h"
fazekasgy@37 38 #include "PyPlugScanner.h"
fazekasgy@37 39 #include "PyPlugin.h"
fazekasgy@37 40 #include "PyExtensionModule.h"
fazekasgy@37 41 #include "PyExtensionManager.h"
gyorgyf@63 42 #include <sstream>
fazekasgy@37 43
fazekasgy@37 44 #ifdef _WIN32
fazekasgy@37 45 #define pathsep ('\\')
fazekasgy@37 46 #include <windows.h>
fazekasgy@37 47 #include <tchar.h>
fazekasgy@37 48 #else
fazekasgy@37 49 #define pathsep ('/')
fazekasgy@37 50 #include <dirent.h>
fazekasgy@37 51 #include <dlfcn.h>
fazekasgy@37 52 #endif
fazekasgy@37 53
fazekasgy@37 54 using std::cerr;
fazekasgy@37 55 using std::endl;
fazekasgy@37 56 using std::string;
fazekasgy@37 57 using std::vector;
fazekasgy@37 58
fazekasgy@37 59 static int adinstcount;
fazekasgy@37 60 static int totinstcount;
fazekasgy@51 61 static bool numpyInstalled = false;
fazekasgy@51 62 static bool arrayApiInitialised = false;
fazekasgy@37 63
fazekasgy@37 64 class PyPluginAdapter : public Vamp::PluginAdapterBase
fazekasgy@37 65 {
fazekasgy@37 66 public:
fazekasgy@37 67 PyPluginAdapter(std::string pyPlugId, PyObject* pyClass) :
fazekasgy@37 68 PluginAdapterBase(),
fazekasgy@37 69 m_plug(pyPlugId),
fazekasgy@37 70 m_pyClass(pyClass),
fazekasgy@37 71 m_failed(false)
fazekasgy@37 72 {
fazekasgy@37 73 cerr << "PyPluginAdapter:ctor:"<< adinstcount << ": " << m_plug << endl;
fazekasgy@37 74 adinstcount++;
fazekasgy@37 75 }
fazekasgy@37 76
fazekasgy@37 77 ~PyPluginAdapter()
fazekasgy@37 78 {
fazekasgy@37 79 }
fazekasgy@37 80
fazekasgy@37 81 bool failed() { return m_failed; }
fazekasgy@37 82 std::string getPlugKey() { return m_plug; }
fazekasgy@37 83
fazekasgy@37 84 protected:
fazekasgy@37 85 Vamp::Plugin *createPlugin(float inputSampleRate)
fazekasgy@37 86 {
fazekasgy@37 87 try {
fazekasgy@51 88 PyPlugin *plugin = new PyPlugin(m_plug, inputSampleRate, m_pyClass, totinstcount, numpyInstalled);
fazekasgy@37 89 return plugin;
fazekasgy@37 90 } catch (...) {
fazekasgy@37 91 cerr << "PyPluginAdapter::createPlugin: Failed to construct PyPlugin" << endl;
fazekasgy@37 92 // any plugin with syntax errors will fail to construct
fazekasgy@37 93 m_failed = true;
fazekasgy@37 94 return 0;
fazekasgy@37 95 }
fazekasgy@37 96 }
fazekasgy@37 97
fazekasgy@37 98 std::string m_plug;
fazekasgy@37 99 PyObject *m_pyClass;
fazekasgy@37 100 bool m_failed;
fazekasgy@37 101 };
fazekasgy@37 102
fazekasgy@51 103
fazekasgy@37 104 static void array_API_initialiser()
fazekasgy@37 105 {
fazekasgy@51 106 if (arrayApiInitialised) return;
fazekasgy@51 107
fazekasgy@51 108 /* Numpy 1.3 build note: there seems to be a bug
fazekasgy@51 109 in this version (at least on OS/X) which will cause memory
fazekasgy@51 110 access error in the array API import function if an earlier runtime
fazekasgy@51 111 version of Numpy is used when loading the library.
fazekasgy@51 112 (below is a horrible workaround)
fazekasgy@51 113 */
fazekasgy@51 114
fazekasgy@37 115 #ifdef HAVE_NUMPY
fazekasgy@51 116
fazekasgy@51 117 string ver;
gyorgyf@63 118 std::istringstream verStream;
fazekasgy@51 119 float numpyVersion;
fazekasgy@51 120
fazekasgy@51 121 /// attmept to test numpy version before importing the array API
fazekasgy@51 122 cerr << "Numpy build information: ABI level: " << NPY_VERSION
fazekasgy@51 123 << " Numpy version: " << NUMPY_SHORTVERSION << endl;
fazekasgy@51 124
fazekasgy@51 125 PyObject *pyModule, *pyDict, *pyVer;
fazekasgy@51 126
fazekasgy@51 127 pyModule = PyImport_ImportModule("numpy"); //numpy.core.multiarray
fazekasgy@51 128 if (!pyModule) {
fazekasgy@51 129 cerr << "Vampy was compiled with Numpy support but Numpy does not seem to be installed." << endl;
fazekasgy@51 130 #ifdef __APPLE__
fazekasgy@51 131 cerr << "Hint: Check if Numpy is installed for the particular setup of Python used by Vampy (given by Python exec prefix)." << endl;
fazekasgy@51 132 #endif
fazekasgy@51 133 goto numpyFailure;
fazekasgy@51 134 }
fazekasgy@51 135
fazekasgy@51 136 pyDict = PyModule_GetDict(pyModule); // borrowed ref
fazekasgy@51 137 if (!pyDict) {
fazekasgy@51 138 cerr << "Can not access Numpy module dictionary." << endl;
fazekasgy@51 139 goto numpyFailure;
fazekasgy@51 140 }
fazekasgy@51 141
fazekasgy@51 142 pyVer = PyDict_GetItemString(pyDict,"__version__"); //borrowed ref
fazekasgy@51 143 if (!pyVer) {
fazekasgy@51 144 cerr << "Can not access Numpy version information." << endl;
fazekasgy@51 145 goto numpyFailure;
fazekasgy@51 146 }
fazekasgy@51 147
fazekasgy@51 148 ver = PyString_AsString(pyVer);
fazekasgy@51 149 ver = ver.substr(0,ver.rfind("."));
gyorgyf@63 150 /*
gyorgyf@63 151 Applied patch from here: http://vamp-plugins.org/forum/index.php/topic,162.msg387.html#msg387 to replace this:
gyorgyf@63 152 if(EOF == sscanf(ver.c_str(), "%f", &numpyVersion))
gyorgyf@63 153 {
gyorgyf@63 154 cerr << "Could not parse Numpy version information." << endl;
gyorgyf@63 155 goto numpyFailure;
gyorgyf@63 156 }*/
gyorgyf@63 157 // parse version string to float
gyorgyf@63 158 verStream.str(ver);
gyorgyf@63 159 verStream >> numpyVersion;
fazekasgy@51 160
fazekasgy@51 161 cerr << "Numpy runtime version: " << numpyVersion << endl;
fazekasgy@51 162 if (numpyVersion < (float) NUMPY_SHORTVERSION) {
fazekasgy@51 163 cerr << "Incompatible Numpy version found: " << numpyVersion << endl;
fazekasgy@51 164 goto numpyFailure;
fazekasgy@51 165 }
fazekasgy@51 166
fazekasgy@51 167 Py_DECREF(pyModule);
fazekasgy@51 168
fazekasgy@51 169 // At least we catch import errors, but if binary compatibility
fazekasgy@51 170 // has changed without notice, this would still fail.
fazekasgy@51 171 // However, we should never get to this point now anyway.
fazekasgy@37 172 import_array();
fazekasgy@51 173 if (PyErr_Occurred()) {
fazekasgy@51 174 cerr << "Import error while loading the Numpy Array API." << endl;
fazekasgy@51 175 PyErr_Print(); PyErr_Clear();
fazekasgy@51 176 goto numpyFailure;
fazekasgy@51 177 }
fazekasgy@51 178 else {
fazekasgy@51 179
fazekasgy@51 180 #ifdef _DEBUG
fazekasgy@51 181 if (NPY_VERSION != PyArray_GetNDArrayCVersion()) {
fazekasgy@51 182 // the Import function does this check already.
fazekasgy@51 183 cerr << "Warning: Numpy version mismatch. (Build version: "
fazekasgy@51 184 << NPY_VERSION << " Runtime version: " << PyArray_GetNDArrayCVersion() << ")" << endl;
fazekasgy@51 185 goto numpyFailure;
fazekasgy@51 186 }
fazekasgy@37 187 #endif
fazekasgy@51 188
fazekasgy@51 189 numpyInstalled = true;
fazekasgy@51 190 arrayApiInitialised = true;
fazekasgy@51 191 return;
fazekasgy@51 192 }
fazekasgy@51 193
fazekasgy@51 194
fazekasgy@51 195 numpyFailure:
fazekasgy@51 196 cerr << "Please make sure you have Numpy " << NUMPY_SHORTVERSION << " or greater installed." << endl;
fazekasgy@51 197 cerr << "Vampy: Numpy support disabled." << endl;
fazekasgy@51 198 numpyInstalled = false;
fazekasgy@51 199 arrayApiInitialised = true;
fazekasgy@51 200 if (pyModule) Py_XDECREF(pyModule);
fazekasgy@51 201 return;
fazekasgy@51 202
fazekasgy@51 203 /*HAVE_NUMPY*/
fazekasgy@51 204 #endif
fazekasgy@51 205
fazekasgy@51 206 numpyInstalled = false;
fazekasgy@51 207 arrayApiInitialised = true;
fazekasgy@51 208 return;
fazekasgy@37 209 }
fazekasgy@37 210
fazekasgy@37 211
fazekasgy@37 212 static std::vector<PyPluginAdapter *> adapters;
fazekasgy@37 213 static bool haveScannedPlugins = false;
fazekasgy@37 214
fazekasgy@37 215 static bool tryPreload(string name)
fazekasgy@37 216 {
cannam@53 217 // cerr << "tryPreload: " << name << endl;
fazekasgy@37 218 #ifdef _WIN32
fazekasgy@37 219 void *lib = LoadLibrary(name.c_str());
fazekasgy@37 220 if (!lib) {
fazekasgy@37 221 return false;
fazekasgy@37 222 }
fazekasgy@37 223 #else
fazekasgy@37 224 void *lib = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL);
fazekasgy@37 225 if (!lib) {
cannam@56 226 //perror("dlopen");
fazekasgy@37 227 return false;
fazekasgy@37 228 }
fazekasgy@37 229 #endif
cannam@53 230 cerr << "Preloaded Python from " << name << endl;
fazekasgy@37 231 return true;
fazekasgy@37 232 }
fazekasgy@37 233
fazekasgy@37 234 static bool preloadPython()
fazekasgy@37 235 {
fazekasgy@37 236 #ifdef _WIN32
fazekasgy@37 237 // this doesn't seem to be necessary at all on Windows
fazekasgy@37 238 return true;
fazekasgy@37 239 #endif
fazekasgy@37 240
fazekasgy@37 241 string pyver = Py_GetVersion();
fazekasgy@37 242 int dots = 2;
fazekasgy@37 243 string shortver;
fazekasgy@37 244 for (size_t i = 0; i < pyver.length(); ++i) {
fazekasgy@37 245 if (pyver[i] == '.') {
fazekasgy@37 246 if (--dots == 0) {
fazekasgy@37 247 shortver = pyver.substr(0, i);
fazekasgy@37 248 break;
fazekasgy@37 249 }
fazekasgy@37 250 }
fazekasgy@37 251 }
fazekasgy@37 252 cerr << "Short version: " << shortver << endl;
fazekasgy@37 253 // this is useful to find out where the loaded library might be loaded from
fazekasgy@37 254 cerr << "Python exec prefix: " << Py_GetExecPrefix() << endl;
fazekasgy@37 255
cannam@55 256 char *pylib = getenv("VAMPY_PYLIB");
cannam@54 257 if (pylib && *pylib) {
cannam@54 258 cerr << "Trying to preload Python from specified location " << pylib
cannam@54 259 << "..." << endl;
cannam@54 260 return tryPreload(string(pylib));
cannam@54 261 }
cannam@54 262
fazekasgy@37 263 vector<string> pfxs;
cannam@53 264 pfxs.push_back(string(Py_GetExecPrefix()) + "/");
cannam@53 265 pfxs.push_back(string(Py_GetExecPrefix()) + "/lib/");
fazekasgy@37 266 pfxs.push_back("");
fazekasgy@37 267 pfxs.push_back("/usr/lib/");
fazekasgy@37 268 pfxs.push_back("/usr/local/lib/");
fazekasgy@37 269 char buffer[5];
fazekasgy@37 270
fazekasgy@37 271 // hahaha! grossness is like a brother to us
fazekasgy@37 272 #ifdef __APPLE__
fazekasgy@37 273 for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) {
cannam@53 274 // cerr << "prefix: " << pfxs[pfxidx] << endl;
cannam@53 275 if (tryPreload(pfxs[pfxidx] + string("Python"))) return true;
fazekasgy@37 276 for (int minor = 8; minor >= 0; --minor) {
fazekasgy@37 277 sprintf(buffer, "%d", minor);
fazekasgy@37 278 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib." + buffer)) return true;
fazekasgy@37 279 }
fazekasgy@37 280 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib")) return true;
fazekasgy@37 281 if (tryPreload(pfxs[pfxidx] + string("libpython.dylib"))) return true;
fazekasgy@37 282 }
fazekasgy@37 283 #else
fazekasgy@37 284 for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) {
fazekasgy@37 285 for (int minor = 8; minor >= 0; --minor) {
fazekasgy@37 286 sprintf(buffer, "%d", minor);
fazekasgy@37 287 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so." + buffer)) return true;
fazekasgy@37 288 }
fazekasgy@37 289 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so")) return true;
fazekasgy@37 290 if (tryPreload(pfxs[pfxidx] + string("libpython.so"))) return true;
fazekasgy@37 291 }
fazekasgy@37 292 #endif
fazekasgy@37 293
fazekasgy@37 294 return false;
fazekasgy@37 295 }
fazekasgy@37 296
fazekasgy@37 297
fazekasgy@37 298 static PyExtensionManager pyExtensionManager;
fazekasgy@37 299
fazekasgy@37 300 const VampPluginDescriptor
fazekasgy@37 301 *vampGetPluginDescriptor(unsigned int version,unsigned int index)
fazekasgy@37 302 {
fazekasgy@37 303 if (version < 1) return 0;
fazekasgy@37 304
fazekasgy@37 305 int isPythonInitialized = Py_IsInitialized();
fazekasgy@37 306 cerr << "# isPythonInitialized: " << isPythonInitialized << endl;
fazekasgy@37 307 cerr << "# haveScannedPlugins: " << haveScannedPlugins << endl;
fazekasgy@37 308
fazekasgy@37 309 if (!haveScannedPlugins) {
fazekasgy@37 310
fazekasgy@37 311 if (!isPythonInitialized){
fazekasgy@37 312
fazekasgy@37 313 if (!preloadPython())
fazekasgy@37 314 cerr << "Warning: Could not preload Python. Dynamic loading in scripts will fail." << endl;
fazekasgy@37 315 if (PyImport_AppendInittab("vampy",initvampy) != 0)
fazekasgy@37 316 cerr << "Warning: Extension module could not be added to module inittab." << endl;
fazekasgy@37 317 Py_Initialize();
fazekasgy@51 318 array_API_initialiser();
fazekasgy@37 319 initvampy();
fazekasgy@37 320 #ifdef _DEBUG
fazekasgy@37 321 cerr << "# isPythonInitialized after initialize: " << Py_IsInitialized() << endl;
fazekasgy@37 322 #endif
fazekasgy@37 323 }
fazekasgy@37 324
fazekasgy@37 325 vector<string> pyPlugs;
fazekasgy@37 326 vector<string> pyPath;
fazekasgy@37 327 vector<PyObject *> pyClasses;
fazekasgy@37 328 static PyPlugScanner *scanner;
fazekasgy@37 329
fazekasgy@37 330 //Scanning Plugins
fazekasgy@37 331 cerr << "Scanning Vampy Plugins" << endl;
fazekasgy@37 332 scanner = PyPlugScanner::getInstance();
fazekasgy@37 333
fazekasgy@37 334 // added env. varable support VAMPY_EXTPATH
fazekasgy@37 335 pyPath=scanner->getAllValidPath();
fazekasgy@37 336 scanner->setPath(pyPath);
fazekasgy@37 337
fazekasgy@37 338 // added env. variable support:
fazekasgy@37 339 // VAMPY_COMPILED=1 to recognise .pyc files (default is 1)
fazekasgy@37 340 pyPlugs = scanner->getPyPlugs();
fazekasgy@37 341
fazekasgy@37 342 cerr << "Found " << pyPlugs.size() << " Scripts." << endl;
fazekasgy@37 343 //TODO: should this support multiple classes per script (?)
fazekasgy@37 344 pyClasses = scanner->getPyClasses();
fazekasgy@37 345 cerr << "Found " << pyClasses.size() << " Classes." << endl;
fazekasgy@37 346
fazekasgy@37 347 for (size_t i = 0; i < pyPlugs.size(); ++i) {
fazekasgy@37 348 adapters.push_back( new PyPluginAdapter(pyPlugs[i],pyClasses[i]));
fazekasgy@37 349 }
fazekasgy@37 350 pyExtensionManager.setPlugModuleNames(pyPlugs);
fazekasgy@37 351 pyExtensionManager.initExtension();
fazekasgy@37 352 array_API_initialiser();
fazekasgy@37 353 haveScannedPlugins=true;
fazekasgy@37 354 }
fazekasgy@37 355
fazekasgy@37 356 #ifdef _DEBUG
fazekasgy@37 357 cerr << "Accessing adapter index: " << index << " (adapters: " << adapters.size() << ")" << endl;
fazekasgy@37 358 #endif
fazekasgy@37 359
fazekasgy@37 360 if (index<adapters.size()) {
fazekasgy@37 361
fazekasgy@37 362 const VampPluginDescriptor *tmp = adapters[index]->getDescriptor();
fazekasgy@37 363
fazekasgy@37 364 if (adapters[index]->failed()) {
fazekasgy@37 365 cerr << "\nERROR: [in vampGetPluginDescriptor] Removing adapter of: \n'"
fazekasgy@37 366 << adapters[index]->getPlugKey() << "'\n"
fazekasgy@37 367 << "The plugin has failed to construct. Hint: Check __init__() function." << endl;
fazekasgy@37 368 pyExtensionManager.deleteModuleName(adapters[index]->getPlugKey());
fazekasgy@37 369 delete adapters[index];
fazekasgy@37 370 adapters.erase(adapters.begin()+index);
fazekasgy@37 371 return 0;
fazekasgy@37 372 }
fazekasgy@37 373
fazekasgy@37 374 return tmp;
fazekasgy@37 375
fazekasgy@37 376 } else return 0;
fazekasgy@37 377 }
fazekasgy@37 378
fazekasgy@37 379
fazekasgy@37 380
fazekasgy@37 381
fazekasgy@37 382
fazekasgy@37 383
fazekasgy@37 384
fazekasgy@37 385