annotate PyPlugScanner.cpp @ 66:5664fe298af2

Update to Python 2.7 and clean up the build (avoid using deprecated NumPy API, fix compiler warnings)
author Chris Cannam
date Mon, 17 Nov 2014 09:37:59 +0000
parents 62dcaa5fe6f8
children 6c755f3e1173
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
fazekasgy@37 13 #include "PyPlugScanner.h"
fazekasgy@37 14 #include <algorithm>
fazekasgy@37 15 #include <cstdlib>
fazekasgy@37 16 //#include "vamp-hostsdk/PluginHostAdapter.h"
fazekasgy@37 17
fazekasgy@37 18 #ifdef _WIN32
fazekasgy@37 19 #include <windows.h>
fazekasgy@37 20 #include <tchar.h>
fazekasgy@37 21 #define pathsep ("\\")
fazekasgy@37 22 #else
fazekasgy@37 23 #include <dirent.h>
fazekasgy@37 24 #include <dlfcn.h>
fazekasgy@37 25 #define pathsep ("/")
fazekasgy@37 26 #endif
fazekasgy@37 27 #define joinPath(a,b) ( (a)+pathsep+(b) )
fazekasgy@37 28
fazekasgy@37 29 using std::string;
fazekasgy@37 30 using std::vector;
fazekasgy@37 31 using std::cerr;
fazekasgy@37 32 using std::endl;
fazekasgy@37 33 using std::find;
fazekasgy@37 34
fazekasgy@37 35 PyPlugScanner::PyPlugScanner()
fazekasgy@37 36 {
fazekasgy@37 37
fazekasgy@37 38 }
fazekasgy@37 39
fazekasgy@37 40 PyPlugScanner *PyPlugScanner::m_instance = NULL;
fazekasgy@37 41 bool PyPlugScanner::m_hasInstance = false;
fazekasgy@37 42
fazekasgy@37 43 PyPlugScanner*
fazekasgy@37 44 PyPlugScanner::getInstance()
fazekasgy@37 45 {
fazekasgy@37 46 if (!m_hasInstance) {
fazekasgy@37 47 m_instance = new PyPlugScanner();
fazekasgy@37 48 m_hasInstance = true;
fazekasgy@37 49 }
fazekasgy@37 50 return m_instance;
fazekasgy@37 51 }
fazekasgy@37 52
fazekasgy@37 53 void
fazekasgy@37 54 PyPlugScanner::setPath(vector<string> path)
fazekasgy@37 55 {
fazekasgy@37 56 m_path=path;
fazekasgy@37 57 }
fazekasgy@37 58
fazekasgy@37 59 // We assume that each script on the path has one valid class
fazekasgy@37 60 vector<string>
fazekasgy@37 61 PyPlugScanner::getPyPlugs()
fazekasgy@37 62 {
fazekasgy@37 63 //for_each m_path listFiles then return vector<pyPlugs>
fazekasgy@37 64 //key format: FullPathString/FileName.py:ClassName
fazekasgy@37 65
fazekasgy@37 66 bool getCompiled = true;
fazekasgy@37 67 char* getPyc = getenv("VAMPY_COMPILED");
fazekasgy@37 68 if (getPyc) {
fazekasgy@37 69 string value(getPyc);
fazekasgy@37 70 cerr << "VAMPY_COMPILED=" << value << endl;
fazekasgy@37 71 getCompiled = value.compare("1")?false:true;
fazekasgy@37 72 }
fazekasgy@37 73
fazekasgy@37 74 vector<string> pyPlugs;
fazekasgy@37 75 string pluginKey;
fazekasgy@37 76 PyObject *pyClass;
fazekasgy@37 77
fazekasgy@37 78 for (size_t i = 0; i < m_path.size(); ++i) {
fazekasgy@37 79
fazekasgy@37 80 vector<string> files = listFiles(m_path[i],"py");
fazekasgy@37 81
fazekasgy@37 82 /// recognise byte compiled plugins
fazekasgy@37 83 if (getCompiled) {
fazekasgy@58 84 vector<string> pyc_files = listFiles(m_path[i],"pyc");
fazekasgy@58 85 vector<string> pyo_files = listFiles(m_path[i],"pyo");
fazekasgy@58 86 mergeFileLists(pyc_files,pyo_files,".pyo");
fazekasgy@58 87 mergeFileLists(pyo_files,files,".py");
fazekasgy@37 88 }
fazekasgy@37 89
fazekasgy@37 90 for (vector<string>::iterator fi = files.begin();
fazekasgy@37 91 fi != files.end(); ++fi) {
fazekasgy@37 92 string script = *fi;
fazekasgy@37 93 if (!script.empty()) {
fazekasgy@37 94 string classname=script.substr(0,script.rfind('.'));
fazekasgy@37 95 pluginKey=joinPath(m_path[i],script)+":"+classname;
fazekasgy@37 96 pyClass = getScriptClass(m_path[i],classname);
fazekasgy@37 97 if (pyClass == NULL)
fazekasgy@37 98 cerr << "Warning: Syntax error in VamPy plugin: "
fazekasgy@37 99 << classname << ". Avoiding plugin." << endl;
fazekasgy@37 100 else {
fazekasgy@37 101 pyPlugs.push_back(pluginKey);
fazekasgy@37 102 m_pyClasses.push_back(pyClass);
fazekasgy@37 103 }
fazekasgy@37 104 //pyPlugs.push_back(pluginKey);
fazekasgy@37 105 }
fazekasgy@37 106 }
fazekasgy@37 107 }
fazekasgy@37 108
fazekasgy@37 109 return pyPlugs;
fazekasgy@37 110
fazekasgy@37 111 }
fazekasgy@37 112
fazekasgy@37 113 /// insert python byte code names (.pyc) if a .py file can not be found
fazekasgy@37 114 /// The interpreter automatically generates byte code files and executes
fazekasgy@37 115 /// them if they exist. Therefore, we prefer .py files, but we allow
fazekasgy@37 116 /// (relatively) closed source distributions by recognising .pyc files.
fazekasgy@37 117 void
fazekasgy@58 118 PyPlugScanner::mergeFileLists(vector<string> &src, vector<string> &tg, string target_ext)
fazekasgy@37 119 {
fazekasgy@58 120 for (vector<string>::iterator srcit = src.begin();
fazekasgy@58 121 srcit != src.end(); ++srcit) {
fazekasgy@58 122 // cerr << *srcit;
fazekasgy@58 123 string src_name = *srcit;
fazekasgy@58 124 string tg_name = src_name.substr(0,src_name.rfind('.')) + target_ext;
fazekasgy@58 125 vector<string>::iterator tgit = find (tg.begin(), tg.end(), tg_name);
fazekasgy@58 126 if (tgit == tg.end()) tg.push_back(src_name);
fazekasgy@37 127 }
fazekasgy@37 128
fazekasgy@37 129 }
fazekasgy@37 130
fazekasgy@37 131
fazekasgy@37 132 //For now return one class object found in each script
fazekasgy@37 133 vector<PyObject*>
fazekasgy@37 134 PyPlugScanner::getPyClasses()
fazekasgy@37 135 {
fazekasgy@37 136 return m_pyClasses;
fazekasgy@37 137
fazekasgy@37 138 }
fazekasgy@37 139
fazekasgy@37 140 //Validate
fazekasgy@37 141 //This should not be called more than once!
fazekasgy@37 142 PyObject*
fazekasgy@37 143 PyPlugScanner::getScriptClass(string path, string classname)
fazekasgy@37 144 {
fazekasgy@37 145
fazekasgy@37 146 //Add plugin path to active Python Path
fazekasgy@37 147 string pyCmd = "import sys\nsys.path.append('" + path + "')\n";
fazekasgy@37 148 PyRun_SimpleString(pyCmd.c_str());
fazekasgy@37 149
fazekasgy@37 150 //Assign an object to the source code
fazekasgy@37 151 PyObject *pySource = PyString_FromString(classname.c_str());
fazekasgy@37 152
fazekasgy@37 153 //Import it as a module into the py interpreter
fazekasgy@37 154 PyObject *pyModule = PyImport_Import(pySource);
fazekasgy@37 155 PyObject* pyError = PyErr_Occurred();
fazekasgy@37 156 if (! pyError == 0) {
fazekasgy@37 157 cerr << "ERROR: error importing source: " << classname << endl;
fazekasgy@37 158 PyErr_Print();
fazekasgy@37 159 Py_DECREF(pySource);
fazekasgy@37 160 Py_CLEAR(pyModule); // safer if pyModule==NULL
fazekasgy@37 161 return NULL;
fazekasgy@37 162 }
fazekasgy@37 163 Py_DECREF(pySource);
fazekasgy@37 164
fazekasgy@37 165 //Read the dictionary object holding the namespace of the module (borrowed reference)
fazekasgy@37 166 PyObject *pyDict = PyModule_GetDict(pyModule);
fazekasgy@37 167 Py_DECREF(pyModule);
fazekasgy@37 168
fazekasgy@37 169 //Get the PluginClass from the module (borrowed reference)
fazekasgy@37 170 PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str());
fazekasgy@37 171
fazekasgy@37 172 //Check if class is present and a callable method is implemented
fazekasgy@37 173 if (pyClass && PyCallable_Check(pyClass)) {
fazekasgy@37 174
fazekasgy@37 175 return pyClass;
fazekasgy@37 176 }
fazekasgy@37 177 else {
fazekasgy@37 178 cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl
fazekasgy@37 179 << "Hint: plugin source filename and plugin class name must be the same." << endl;
fazekasgy@37 180 PyErr_Print();
fazekasgy@37 181 return NULL;
fazekasgy@37 182 }
fazekasgy@37 183 }
fazekasgy@37 184
fazekasgy@37 185
fazekasgy@37 186
fazekasgy@37 187 // Return a list of files in dir with given extension
fazekasgy@37 188 // Code taken from hostext/PluginLoader.cpp
fazekasgy@37 189 vector<string>
fazekasgy@37 190 PyPlugScanner::listFiles(string dir, string extension)
fazekasgy@37 191 {
fazekasgy@37 192 vector<string> files;
fazekasgy@37 193
fazekasgy@37 194 #ifdef _WIN32
fazekasgy@37 195
fazekasgy@37 196 string expression = dir + "\\*." + extension;
fazekasgy@37 197 WIN32_FIND_DATA data;
fazekasgy@37 198 HANDLE fh = FindFirstFile(expression.c_str(), &data);
fazekasgy@37 199 if (fh == INVALID_HANDLE_VALUE) return files;
fazekasgy@37 200
fazekasgy@37 201 bool ok = true;
fazekasgy@37 202 while (ok) {
fazekasgy@37 203 files.push_back(data.cFileName);
fazekasgy@37 204 ok = FindNextFile(fh, &data);
fazekasgy@37 205 }
fazekasgy@37 206
fazekasgy@37 207 FindClose(fh);
fazekasgy@37 208
fazekasgy@37 209 #else
fazekasgy@37 210
fazekasgy@37 211 size_t extlen = extension.length();
fazekasgy@37 212 DIR *d = opendir(dir.c_str());
fazekasgy@37 213 if (!d) return files;
fazekasgy@37 214
fazekasgy@37 215 struct dirent *e = 0;
fazekasgy@37 216 while ((e = readdir(d))) {
fazekasgy@37 217
fazekasgy@37 218 if (!e->d_name) continue;
fazekasgy@37 219
fazekasgy@37 220 size_t len = strlen(e->d_name);
fazekasgy@37 221 if (len < extlen + 2 ||
fazekasgy@37 222 e->d_name + len - extlen - 1 != "." + extension) {
fazekasgy@37 223 continue;
fazekasgy@37 224 }
fazekasgy@37 225 //cerr << "pyscripts: " << e->d_name << endl;
fazekasgy@37 226 files.push_back(e->d_name);
fazekasgy@37 227 }
fazekasgy@37 228
fazekasgy@37 229 closedir(d);
fazekasgy@37 230 #endif
fazekasgy@37 231
fazekasgy@37 232 return files;
fazekasgy@37 233 }
fazekasgy@37 234
fazekasgy@37 235
fazekasgy@37 236 //!!! It would probably be better to actually call
fazekasgy@37 237 // PluginHostAdapter::getPluginPath. That would mean this "plugin"
fazekasgy@37 238 // needs to link against vamp-hostsdk, but that's probably acceptable
fazekasgy@37 239 // as it is sort of a host as well.
fazekasgy@37 240
fazekasgy@37 241 // std::vector<std::string>
fazekasgy@37 242 // PyPlugScanner::getAllValidPath()
fazekasgy@37 243 // {
fazekasgy@37 244 // Vamp::PluginHostAdapter host_adapter( ??? );
fazekasgy@37 245 // return host_adapter.getPluginPath();
fazekasgy@37 246 // }
fazekasgy@37 247
fazekasgy@37 248 // tried to implement it, but found a bit confusing how to
fazekasgy@37 249 // instantiate the host adapter here...
fazekasgy@37 250
fazekasgy@37 251
fazekasgy@37 252 //Return correct plugin directories as per platform
fazekasgy@37 253 //Code taken from vamp-sdk/PluginHostAdapter.cpp
fazekasgy@37 254 std::vector<std::string>
fazekasgy@37 255 PyPlugScanner::getAllValidPath()
fazekasgy@37 256 {
fazekasgy@37 257
fazekasgy@37 258 std::vector<std::string> path;
fazekasgy@37 259 std::string envPath;
fazekasgy@37 260
fazekasgy@37 261 char *cpath = getenv("VAMP_PATH");
fazekasgy@37 262 if (cpath) envPath = cpath;
fazekasgy@37 263
fazekasgy@37 264 #ifdef _WIN32
fazekasgy@37 265 #define PATH_SEPARATOR ';'
fazekasgy@37 266 #define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins"
fazekasgy@37 267 #else
fazekasgy@37 268 #define PATH_SEPARATOR ':'
fazekasgy@37 269 #ifdef __APPLE__
fazekasgy@37 270 #define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp"
fazekasgy@37 271 #else
fazekasgy@37 272 #define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp"
fazekasgy@37 273 #endif
fazekasgy@37 274 #endif
fazekasgy@37 275
fazekasgy@37 276 if (envPath == "") {
fazekasgy@37 277 envPath = DEFAULT_VAMP_PATH;
fazekasgy@37 278 char *chome = getenv("HOME");
fazekasgy@37 279 if (chome) {
fazekasgy@37 280 std::string home(chome);
fazekasgy@37 281 std::string::size_type f;
fazekasgy@37 282 while ((f = envPath.find("$HOME")) != std::string::npos &&
fazekasgy@37 283 f < envPath.length()) {
fazekasgy@37 284 envPath.replace(f, 5, home);
fazekasgy@37 285 }
fazekasgy@37 286 }
fazekasgy@37 287 #ifdef _WIN32
fazekasgy@37 288 char *cpfiles = getenv("ProgramFiles");
fazekasgy@37 289 if (!cpfiles) cpfiles = "C:\\Program Files";
fazekasgy@37 290 std::string pfiles(cpfiles);
fazekasgy@37 291 std::string::size_type f;
fazekasgy@37 292 while ((f = envPath.find("%ProgramFiles%")) != std::string::npos &&
fazekasgy@37 293 f < envPath.length()) {
fazekasgy@37 294 envPath.replace(f, 14, pfiles);
fazekasgy@37 295 }
fazekasgy@37 296 #endif
fazekasgy@37 297 }
fazekasgy@37 298
fazekasgy@37 299 std::string::size_type index = 0, newindex = 0;
fazekasgy@37 300
fazekasgy@37 301 while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) {
fazekasgy@37 302 path.push_back(envPath.substr(index, newindex - index));
fazekasgy@37 303 index = newindex + 1;
fazekasgy@37 304 }
fazekasgy@37 305
fazekasgy@37 306 path.push_back(envPath.substr(index));
fazekasgy@37 307
fazekasgy@37 308 //can add an extra path for vampy plugins
fazekasgy@37 309 char* extraPath = getenv("VAMPY_EXTPATH");
fazekasgy@37 310 if (extraPath) {
fazekasgy@37 311 string vampyPath(extraPath);
fazekasgy@37 312 cerr << "VAMPY_EXTPATH=" << vampyPath << endl;
fazekasgy@37 313 path.push_back(vampyPath);
fazekasgy@37 314 }
fazekasgy@37 315
fazekasgy@37 316 return path;
fazekasgy@37 317 }