fazekasgy@37: /* fazekasgy@37: fazekasgy@37: * Vampy : This plugin is a wrapper around the Vamp plugin API. fazekasgy@37: * It allows for writing Vamp plugins in Python. fazekasgy@37: fazekasgy@37: * Centre for Digital Music, Queen Mary University of London. fazekasgy@37: * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources fazekasgy@37: * for licence information.) fazekasgy@37: fazekasgy@37: */ fazekasgy@37: fazekasgy@37: fazekasgy@37: #include "PyPlugScanner.h" fazekasgy@37: #include fazekasgy@37: #include fazekasgy@37: //#include "vamp-hostsdk/PluginHostAdapter.h" fazekasgy@37: fazekasgy@37: #ifdef _WIN32 fazekasgy@37: #include fazekasgy@37: #include fazekasgy@37: #define pathsep ("\\") fazekasgy@37: #else fazekasgy@37: #include fazekasgy@37: #include fazekasgy@37: #define pathsep ("/") fazekasgy@37: #endif fazekasgy@37: #define joinPath(a,b) ( (a)+pathsep+(b) ) fazekasgy@37: fazekasgy@37: using std::string; fazekasgy@37: using std::vector; fazekasgy@37: using std::cerr; fazekasgy@37: using std::endl; fazekasgy@37: using std::find; fazekasgy@37: fazekasgy@37: PyPlugScanner::PyPlugScanner() fazekasgy@37: { fazekasgy@37: fazekasgy@37: } fazekasgy@37: fazekasgy@37: PyPlugScanner *PyPlugScanner::m_instance = NULL; fazekasgy@37: bool PyPlugScanner::m_hasInstance = false; fazekasgy@37: fazekasgy@37: PyPlugScanner* fazekasgy@37: PyPlugScanner::getInstance() fazekasgy@37: { fazekasgy@37: if (!m_hasInstance) { fazekasgy@37: m_instance = new PyPlugScanner(); fazekasgy@37: m_hasInstance = true; fazekasgy@37: } fazekasgy@37: return m_instance; fazekasgy@37: } fazekasgy@37: fazekasgy@37: void fazekasgy@37: PyPlugScanner::setPath(vector path) fazekasgy@37: { fazekasgy@37: m_path=path; fazekasgy@37: } fazekasgy@37: fazekasgy@37: // We assume that each script on the path has one valid class fazekasgy@37: vector fazekasgy@37: PyPlugScanner::getPyPlugs() fazekasgy@37: { fazekasgy@37: //for_each m_path listFiles then return vector fazekasgy@37: //key format: FullPathString/FileName.py:ClassName fazekasgy@37: fazekasgy@37: bool getCompiled = true; fazekasgy@37: char* getPyc = getenv("VAMPY_COMPILED"); fazekasgy@37: if (getPyc) { fazekasgy@37: string value(getPyc); fazekasgy@37: cerr << "VAMPY_COMPILED=" << value << endl; fazekasgy@37: getCompiled = value.compare("1")?false:true; fazekasgy@37: } fazekasgy@37: fazekasgy@37: vector pyPlugs; fazekasgy@37: string pluginKey; fazekasgy@37: PyObject *pyClass; fazekasgy@37: fazekasgy@37: for (size_t i = 0; i < m_path.size(); ++i) { fazekasgy@37: fazekasgy@37: vector files = listFiles(m_path[i],"py"); fazekasgy@37: fazekasgy@37: /// recognise byte compiled plugins fazekasgy@37: if (getCompiled) { fazekasgy@37: vector compiled_files = listFiles(m_path[i],"pyc"); fazekasgy@37: mergeFileLists(compiled_files,files); fazekasgy@37: } fazekasgy@37: fazekasgy@37: for (vector::iterator fi = files.begin(); fazekasgy@37: fi != files.end(); ++fi) { fazekasgy@37: string script = *fi; fazekasgy@37: if (!script.empty()) { fazekasgy@37: string classname=script.substr(0,script.rfind('.')); fazekasgy@37: pluginKey=joinPath(m_path[i],script)+":"+classname; fazekasgy@37: pyClass = getScriptClass(m_path[i],classname); fazekasgy@37: if (pyClass == NULL) fazekasgy@37: cerr << "Warning: Syntax error in VamPy plugin: " fazekasgy@37: << classname << ". Avoiding plugin." << endl; fazekasgy@37: else { fazekasgy@37: pyPlugs.push_back(pluginKey); fazekasgy@37: m_pyClasses.push_back(pyClass); fazekasgy@37: } fazekasgy@37: //pyPlugs.push_back(pluginKey); fazekasgy@37: } fazekasgy@37: } fazekasgy@37: } fazekasgy@37: fazekasgy@37: return pyPlugs; fazekasgy@37: fazekasgy@37: } fazekasgy@37: fazekasgy@37: /// insert python byte code names (.pyc) if a .py file can not be found fazekasgy@37: /// The interpreter automatically generates byte code files and executes fazekasgy@37: /// them if they exist. Therefore, we prefer .py files, but we allow fazekasgy@37: /// (relatively) closed source distributions by recognising .pyc files. fazekasgy@37: void fazekasgy@37: PyPlugScanner::mergeFileLists(vector &pyc, vector &py) fazekasgy@37: { fazekasgy@37: for (vector::iterator pycit = pyc.begin(); fazekasgy@37: pycit != pyc.end(); ++pycit) { fazekasgy@37: // cerr << *pycit; fazekasgy@37: string pyc_name = *pycit; fazekasgy@37: string py_name = pyc_name.substr(0,pyc_name.rfind('.')) + ".py"; fazekasgy@37: vector::iterator pyit = find (py.begin(), py.end(), py_name); fazekasgy@37: if (pyit == py.end()) py.push_back(pyc_name); fazekasgy@37: } fazekasgy@37: fazekasgy@37: } fazekasgy@37: fazekasgy@37: fazekasgy@37: //For now return one class object found in each script fazekasgy@37: vector fazekasgy@37: PyPlugScanner::getPyClasses() fazekasgy@37: { fazekasgy@37: return m_pyClasses; fazekasgy@37: fazekasgy@37: } fazekasgy@37: fazekasgy@37: //Validate fazekasgy@37: //This should not be called more than once! fazekasgy@37: PyObject* fazekasgy@37: PyPlugScanner::getScriptClass(string path, string classname) fazekasgy@37: { fazekasgy@37: fazekasgy@37: //Add plugin path to active Python Path fazekasgy@37: string pyCmd = "import sys\nsys.path.append('" + path + "')\n"; fazekasgy@37: PyRun_SimpleString(pyCmd.c_str()); fazekasgy@37: fazekasgy@37: //Assign an object to the source code fazekasgy@37: PyObject *pySource = PyString_FromString(classname.c_str()); fazekasgy@37: fazekasgy@37: //Import it as a module into the py interpreter fazekasgy@37: PyObject *pyModule = PyImport_Import(pySource); fazekasgy@37: PyObject* pyError = PyErr_Occurred(); fazekasgy@37: if (! pyError == 0) { fazekasgy@37: cerr << "ERROR: error importing source: " << classname << endl; fazekasgy@37: PyErr_Print(); fazekasgy@37: Py_DECREF(pySource); fazekasgy@37: Py_CLEAR(pyModule); // safer if pyModule==NULL fazekasgy@37: return NULL; fazekasgy@37: } fazekasgy@37: Py_DECREF(pySource); fazekasgy@37: fazekasgy@37: //Read the dictionary object holding the namespace of the module (borrowed reference) fazekasgy@37: PyObject *pyDict = PyModule_GetDict(pyModule); fazekasgy@37: Py_DECREF(pyModule); fazekasgy@37: fazekasgy@37: //Get the PluginClass from the module (borrowed reference) fazekasgy@37: PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str()); fazekasgy@37: fazekasgy@37: //Check if class is present and a callable method is implemented fazekasgy@37: if (pyClass && PyCallable_Check(pyClass)) { fazekasgy@37: fazekasgy@37: return pyClass; fazekasgy@37: } fazekasgy@37: else { fazekasgy@37: cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl fazekasgy@37: << "Hint: plugin source filename and plugin class name must be the same." << endl; fazekasgy@37: PyErr_Print(); fazekasgy@37: return NULL; fazekasgy@37: } fazekasgy@37: } fazekasgy@37: fazekasgy@37: fazekasgy@37: fazekasgy@37: // Return a list of files in dir with given extension fazekasgy@37: // Code taken from hostext/PluginLoader.cpp fazekasgy@37: vector fazekasgy@37: PyPlugScanner::listFiles(string dir, string extension) fazekasgy@37: { fazekasgy@37: vector files; fazekasgy@37: fazekasgy@37: #ifdef _WIN32 fazekasgy@37: fazekasgy@37: string expression = dir + "\\*." + extension; fazekasgy@37: WIN32_FIND_DATA data; fazekasgy@37: HANDLE fh = FindFirstFile(expression.c_str(), &data); fazekasgy@37: if (fh == INVALID_HANDLE_VALUE) return files; fazekasgy@37: fazekasgy@37: bool ok = true; fazekasgy@37: while (ok) { fazekasgy@37: files.push_back(data.cFileName); fazekasgy@37: ok = FindNextFile(fh, &data); fazekasgy@37: } fazekasgy@37: fazekasgy@37: FindClose(fh); fazekasgy@37: fazekasgy@37: #else fazekasgy@37: fazekasgy@37: size_t extlen = extension.length(); fazekasgy@37: DIR *d = opendir(dir.c_str()); fazekasgy@37: if (!d) return files; fazekasgy@37: fazekasgy@37: struct dirent *e = 0; fazekasgy@37: while ((e = readdir(d))) { fazekasgy@37: fazekasgy@37: if (!e->d_name) continue; fazekasgy@37: fazekasgy@37: size_t len = strlen(e->d_name); fazekasgy@37: if (len < extlen + 2 || fazekasgy@37: e->d_name + len - extlen - 1 != "." + extension) { fazekasgy@37: continue; fazekasgy@37: } fazekasgy@37: //cerr << "pyscripts: " << e->d_name << endl; fazekasgy@37: files.push_back(e->d_name); fazekasgy@37: } fazekasgy@37: fazekasgy@37: closedir(d); fazekasgy@37: #endif fazekasgy@37: fazekasgy@37: return files; fazekasgy@37: } fazekasgy@37: fazekasgy@37: fazekasgy@37: //!!! It would probably be better to actually call fazekasgy@37: // PluginHostAdapter::getPluginPath. That would mean this "plugin" fazekasgy@37: // needs to link against vamp-hostsdk, but that's probably acceptable fazekasgy@37: // as it is sort of a host as well. fazekasgy@37: fazekasgy@37: // std::vector fazekasgy@37: // PyPlugScanner::getAllValidPath() fazekasgy@37: // { fazekasgy@37: // Vamp::PluginHostAdapter host_adapter( ??? ); fazekasgy@37: // return host_adapter.getPluginPath(); fazekasgy@37: // } fazekasgy@37: fazekasgy@37: // tried to implement it, but found a bit confusing how to fazekasgy@37: // instantiate the host adapter here... fazekasgy@37: fazekasgy@37: fazekasgy@37: //Return correct plugin directories as per platform fazekasgy@37: //Code taken from vamp-sdk/PluginHostAdapter.cpp fazekasgy@37: std::vector fazekasgy@37: PyPlugScanner::getAllValidPath() fazekasgy@37: { fazekasgy@37: fazekasgy@37: std::vector path; fazekasgy@37: std::string envPath; fazekasgy@37: fazekasgy@37: char *cpath = getenv("VAMP_PATH"); fazekasgy@37: if (cpath) envPath = cpath; fazekasgy@37: fazekasgy@37: #ifdef _WIN32 fazekasgy@37: #define PATH_SEPARATOR ';' fazekasgy@37: #define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins" fazekasgy@37: #else fazekasgy@37: #define PATH_SEPARATOR ':' fazekasgy@37: #ifdef __APPLE__ fazekasgy@37: #define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp" fazekasgy@37: #else fazekasgy@37: #define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp" fazekasgy@37: #endif fazekasgy@37: #endif fazekasgy@37: fazekasgy@37: if (envPath == "") { fazekasgy@37: envPath = DEFAULT_VAMP_PATH; fazekasgy@37: char *chome = getenv("HOME"); fazekasgy@37: if (chome) { fazekasgy@37: std::string home(chome); fazekasgy@37: std::string::size_type f; fazekasgy@37: while ((f = envPath.find("$HOME")) != std::string::npos && fazekasgy@37: f < envPath.length()) { fazekasgy@37: envPath.replace(f, 5, home); fazekasgy@37: } fazekasgy@37: } fazekasgy@37: #ifdef _WIN32 fazekasgy@37: char *cpfiles = getenv("ProgramFiles"); fazekasgy@37: if (!cpfiles) cpfiles = "C:\\Program Files"; fazekasgy@37: std::string pfiles(cpfiles); fazekasgy@37: std::string::size_type f; fazekasgy@37: while ((f = envPath.find("%ProgramFiles%")) != std::string::npos && fazekasgy@37: f < envPath.length()) { fazekasgy@37: envPath.replace(f, 14, pfiles); fazekasgy@37: } fazekasgy@37: #endif fazekasgy@37: } fazekasgy@37: fazekasgy@37: std::string::size_type index = 0, newindex = 0; fazekasgy@37: fazekasgy@37: while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) { fazekasgy@37: path.push_back(envPath.substr(index, newindex - index)); fazekasgy@37: index = newindex + 1; fazekasgy@37: } fazekasgy@37: fazekasgy@37: path.push_back(envPath.substr(index)); fazekasgy@37: fazekasgy@37: //can add an extra path for vampy plugins fazekasgy@37: char* extraPath = getenv("VAMPY_EXTPATH"); fazekasgy@37: if (extraPath) { fazekasgy@37: string vampyPath(extraPath); fazekasgy@37: cerr << "VAMPY_EXTPATH=" << vampyPath << endl; fazekasgy@37: path.push_back(vampyPath); fazekasgy@37: } fazekasgy@37: fazekasgy@37: return path; fazekasgy@37: }