fazekasgy@0: /** cannam@7: * This Vamp plugin is a wrapper for Python Scripts. (VamPy) fazekasgy@0: * Centre for Digital Music, Queen Mary, University of London. fazekasgy@0: * Copyright 2008, George Fazekas. fazekasgy@0: fazekasgy@0: */ fazekasgy@0: fazekasgy@0: fazekasgy@0: #include "PyPlugScanner.h" fazekasgy@0: fazekasgy@0: //#include fazekasgy@0: //#include fazekasgy@0: fazekasgy@0: #ifdef _WIN32 fazekasgy@0: #include fazekasgy@0: #include fazekasgy@0: #define pathsep ("\\") fazekasgy@0: #else fazekasgy@0: #include fazekasgy@0: #include fazekasgy@0: #define pathsep ("/") fazekasgy@0: #endif fazekasgy@0: #define joinPath(a,b) ( (a)+pathsep+(b) ) fazekasgy@0: fazekasgy@0: using std::string; fazekasgy@0: using std::vector; fazekasgy@0: using std::cerr; fazekasgy@0: using std::endl; fazekasgy@0: fazekasgy@0: PyPlugScanner::PyPlugScanner() fazekasgy@0: { fazekasgy@0: fazekasgy@0: } fazekasgy@0: fazekasgy@0: PyPlugScanner *PyPlugScanner::m_instance = NULL; fazekasgy@0: bool PyPlugScanner::m_hasInstance = false; fazekasgy@0: fazekasgy@0: PyPlugScanner* fazekasgy@0: PyPlugScanner::getInstance() fazekasgy@0: { fazekasgy@0: if (!m_hasInstance) { fazekasgy@0: m_instance = new PyPlugScanner(); fazekasgy@0: m_hasInstance = true; fazekasgy@0: } fazekasgy@0: return m_instance; fazekasgy@0: } fazekasgy@0: fazekasgy@0: void fazekasgy@0: PyPlugScanner::setPath(vector path) fazekasgy@0: { fazekasgy@0: m_path=path; fazekasgy@0: } fazekasgy@0: fazekasgy@0: // TODO: This should return all scripts for all valid paths fazekasgy@0: // Validate python classes here? fazekasgy@0: // For now, we assume that each script on the path has one valid class fazekasgy@0: vector fazekasgy@0: PyPlugScanner::getPyPlugs() fazekasgy@0: { fazekasgy@0: //foreach m_path listFiles then return vector fazekasgy@0: //format: FullPathString/FileName.py:ClassName fazekasgy@0: fazekasgy@0: vector pyPlugs; fazekasgy@0: string pluginKey; cannam@24: PyObject *pyClass; fazekasgy@0: fazekasgy@0: for (size_t i = 0; i < m_path.size(); ++i) { fazekasgy@0: fazekasgy@0: vector files = listFiles(m_path[i],"py"); fazekasgy@0: fazekasgy@0: for (vector::iterator fi = files.begin(); fazekasgy@0: fi != files.end(); ++fi) { fazekasgy@0: string script = *fi; fazekasgy@0: if (!script.empty()) { fazekasgy@0: string classname=script.substr(0,script.rfind('.')); fazekasgy@0: pluginKey=joinPath(m_path[i],script)+":"+classname; cannam@24: pyClass = getScriptClass(m_path[i],classname); cannam@24: if (pyClass == NULL) fazekasgy@0: cerr << "Warning: Syntax error in VamPy plugin: " cannam@24: << classname << ". Avoiding plugin." << endl; fazekasgy@0: else { fazekasgy@0: pyPlugs.push_back(pluginKey); cannam@24: m_pyClasses.push_back(pyClass); fazekasgy@0: } fazekasgy@0: //pyPlugs.push_back(pluginKey); fazekasgy@0: } fazekasgy@0: } fazekasgy@0: } fazekasgy@0: fazekasgy@0: return pyPlugs; fazekasgy@0: fazekasgy@0: } fazekasgy@0: fazekasgy@0: cannam@24: //For now return one class object found in each script fazekasgy@0: vector cannam@24: PyPlugScanner::getPyClasses() fazekasgy@0: { cannam@24: return m_pyClasses; fazekasgy@0: fazekasgy@0: } fazekasgy@0: fazekasgy@0: fazekasgy@0: //Validate fazekasgy@0: //This should not be called more than once! fazekasgy@0: PyObject* cannam@24: PyPlugScanner::getScriptClass(string path, string classname) fazekasgy@0: { fazekasgy@0: fazekasgy@0: //Add plugin path to active Python Path fazekasgy@0: string pyCmd = "import sys\nsys.path.append('" + path + "')\n"; fazekasgy@25: PyRun_SimpleString(pyCmd.c_str()); fazekasgy@0: fazekasgy@0: //Assign an object to the source code fazekasgy@0: PyObject *pySource = PyString_FromString(classname.c_str()); fazekasgy@0: fazekasgy@0: //Import it as a module into the py interpreter fazekasgy@0: PyObject *pyModule = PyImport_Import(pySource); fazekasgy@0: PyObject* pyError = PyErr_Occurred(); fazekasgy@0: if (! pyError == 0) { fazekasgy@0: cerr << "ERROR: error importing source: " << classname << endl; fazekasgy@0: PyErr_Print(); fazekasgy@0: Py_DECREF(pySource); fazekasgy@0: Py_CLEAR(pyModule); // safer if pyModule==NULL fazekasgy@0: return NULL; fazekasgy@0: } fazekasgy@0: Py_DECREF(pySource); fazekasgy@0: fazekasgy@0: //Read the namespace of the module into a dictionary object (borrowed reference) fazekasgy@0: PyObject *pyDict = PyModule_GetDict(pyModule); fazekasgy@0: Py_DECREF(pyModule); fazekasgy@0: fazekasgy@0: //Get the PluginClass from the module (borrowed reference) fazekasgy@0: PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str()); fazekasgy@0: fazekasgy@0: //Check if class is present and a callable method is implemented fazekasgy@0: if (pyClass && PyCallable_Check(pyClass)) { fazekasgy@0: cannam@24: return pyClass; fazekasgy@8: } fazekasgy@8: else { fazekasgy@8: cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl fazekasgy@8: << "Hint: plugin source filename and plugin class name must be the same." << endl; fazekasgy@8: PyErr_Print(); fazekasgy@8: return NULL; fazekasgy@8: } fazekasgy@0: } fazekasgy@0: fazekasgy@0: fazekasgy@0: fazekasgy@0: // Return a list of files in dir with given extension fazekasgy@0: // Code taken from hostext/PluginLoader.cpp fazekasgy@0: vector fazekasgy@0: PyPlugScanner::listFiles(string dir, string extension) fazekasgy@0: { fazekasgy@0: vector files; fazekasgy@0: fazekasgy@0: #ifdef _WIN32 fazekasgy@0: fazekasgy@0: string expression = dir + "\\*." + extension; fazekasgy@0: WIN32_FIND_DATA data; fazekasgy@0: HANDLE fh = FindFirstFile(expression.c_str(), &data); fazekasgy@0: if (fh == INVALID_HANDLE_VALUE) return files; fazekasgy@0: fazekasgy@0: bool ok = true; fazekasgy@0: while (ok) { fazekasgy@0: files.push_back(data.cFileName); fazekasgy@0: ok = FindNextFile(fh, &data); fazekasgy@0: } fazekasgy@0: fazekasgy@0: FindClose(fh); fazekasgy@0: fazekasgy@0: #else fazekasgy@0: fazekasgy@0: size_t extlen = extension.length(); fazekasgy@0: DIR *d = opendir(dir.c_str()); fazekasgy@0: if (!d) return files; fazekasgy@0: fazekasgy@0: struct dirent *e = 0; fazekasgy@0: while ((e = readdir(d))) { fazekasgy@0: fazekasgy@0: if (!(e->d_type & DT_REG) && (e->d_type != DT_UNKNOWN)) continue; fazekasgy@0: fazekasgy@0: if (!e->d_name) continue; fazekasgy@0: fazekasgy@0: size_t len = strlen(e->d_name); fazekasgy@0: if (len < extlen + 2 || fazekasgy@0: e->d_name + len - extlen - 1 != "." + extension) { fazekasgy@0: continue; fazekasgy@0: } fazekasgy@0: //cerr << "pyscripts: " << e->d_name << endl; fazekasgy@0: files.push_back(e->d_name); fazekasgy@0: } fazekasgy@0: fazekasgy@0: closedir(d); fazekasgy@0: #endif fazekasgy@0: fazekasgy@0: return files; fazekasgy@0: } fazekasgy@0: fazekasgy@0: //Return correct plugin directories as per platform fazekasgy@0: //Code taken from vamp-sdk/PluginHostAdapter.cpp cannam@3: cannam@3: //!!! It would probably be better to actually call cannam@3: // PluginHostAdapter::getPluginPath. That would mean this "plugin" cannam@3: // needs to link against vamp-hostsdk, but that's probably acceptable cannam@3: // as it is sort of a host as well. cannam@3: fazekasgy@0: std::vector fazekasgy@0: PyPlugScanner::getAllValidPath() fazekasgy@0: { fazekasgy@0: std::vector path; fazekasgy@0: std::string envPath; fazekasgy@0: fazekasgy@0: char *cpath = getenv("VAMP_PATH"); fazekasgy@0: if (cpath) envPath = cpath; fazekasgy@0: fazekasgy@0: #ifdef _WIN32 fazekasgy@0: #define PATH_SEPARATOR ';' fazekasgy@0: #define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins" fazekasgy@0: #else fazekasgy@0: #define PATH_SEPARATOR ':' fazekasgy@0: #ifdef __APPLE__ fazekasgy@0: #define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp" fazekasgy@0: #else fazekasgy@0: #define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp" fazekasgy@0: #endif fazekasgy@0: #endif fazekasgy@0: fazekasgy@0: if (envPath == "") { fazekasgy@0: envPath = DEFAULT_VAMP_PATH; fazekasgy@0: char *chome = getenv("HOME"); fazekasgy@0: if (chome) { fazekasgy@0: std::string home(chome); fazekasgy@0: std::string::size_type f; fazekasgy@0: while ((f = envPath.find("$HOME")) != std::string::npos && fazekasgy@0: f < envPath.length()) { fazekasgy@0: envPath.replace(f, 5, home); fazekasgy@0: } fazekasgy@0: } fazekasgy@0: #ifdef _WIN32 fazekasgy@0: char *cpfiles = getenv("ProgramFiles"); fazekasgy@0: if (!cpfiles) cpfiles = "C:\\Program Files"; fazekasgy@0: std::string pfiles(cpfiles); fazekasgy@0: std::string::size_type f; fazekasgy@0: while ((f = envPath.find("%ProgramFiles%")) != std::string::npos && fazekasgy@0: f < envPath.length()) { fazekasgy@0: envPath.replace(f, 14, pfiles); fazekasgy@0: } fazekasgy@0: #endif fazekasgy@0: } fazekasgy@0: fazekasgy@0: std::string::size_type index = 0, newindex = 0; fazekasgy@0: fazekasgy@0: while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) { fazekasgy@0: path.push_back(envPath.substr(index, newindex - index)); fazekasgy@0: index = newindex + 1; fazekasgy@0: } fazekasgy@0: fazekasgy@0: path.push_back(envPath.substr(index)); fazekasgy@0: fazekasgy@0: return path; fazekasgy@0: }