Chris@92: /* -*- c-basic-offset: 8 indent-tabs-mode: t -*- */ 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" Chris@92: #include "PyExtensionManager.h" Chris@92: #include "Debug.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@58: vector pyc_files = listFiles(m_path[i],"pyc"); fazekasgy@58: vector pyo_files = listFiles(m_path[i],"pyo"); fazekasgy@58: mergeFileLists(pyc_files,pyo_files,".pyo"); fazekasgy@58: mergeFileLists(pyo_files,files,".py"); 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) Chris@92: cerr << "Warning: Syntax error or other problem in scanning VamPy plugin: " fazekasgy@37: << classname << ". Avoiding plugin." << endl; fazekasgy@37: else { fazekasgy@37: pyPlugs.push_back(pluginKey); fazekasgy@37: m_pyClasses.push_back(pyClass); Chris@92: } 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@58: PyPlugScanner::mergeFileLists(vector &src, vector &tg, string target_ext) fazekasgy@37: { fazekasgy@58: for (vector::iterator srcit = src.begin(); fazekasgy@58: srcit != src.end(); ++srcit) { fazekasgy@58: // cerr << *srcit; fazekasgy@58: string src_name = *srcit; fazekasgy@58: string tg_name = src_name.substr(0,src_name.rfind('.')) + target_ext; fazekasgy@58: vector::iterator tgit = find (tg.begin(), tg.end(), tg_name); fazekasgy@58: if (tgit == tg.end()) tg.push_back(src_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: //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(); Chris@70: if (pyError) { 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: Chris@92: if (pyClass == Py_None) { Chris@92: DSTREAM << "Vampy: class name " << classname Chris@92: << " is None in module; assuming it was scrubbed " Chris@92: << "following an earlier load failure" << endl; Chris@92: return NULL; Chris@92: } Chris@92: Chris@92: // Check if class is present and a callable method is implemented Chris@92: if (!pyClass || !PyCallable_Check(pyClass)) { 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: } Chris@92: Chris@92: bool acceptable = true; Chris@92: Chris@92: // Check that the module doesn't have any name collisions with Chris@92: // our own symbols Chris@92: Chris@92: int i = 0; Chris@92: while (PyExtensionManager::m_exposedNames[i]) { Chris@92: Chris@92: const char* name = PyExtensionManager::m_exposedNames[i]; Chris@92: i++; Chris@92: Chris@92: PyObject *item = PyDict_GetItemString(pyDict, name); Chris@92: if (!item) continue; Chris@92: Chris@92: if (item == Py_None) { Chris@92: DSTREAM << "Vampy: name " << name << " is None " Chris@92: << "in module " << classname Chris@92: << "; assuming it was cleared on unload" Chris@92: << endl; Chris@92: continue; Chris@92: } Chris@92: Chris@92: PyTypeObject *metatype = Py_TYPE(item); Chris@92: Chris@92: if (!strcmp(name, "frame2RealTime")) { Chris@92: if (metatype != &PyCFunction_Type) { Chris@92: cerr << "ERROR: plugin " << classname Chris@92: << " redefines Vampy function name \"" Chris@92: << name << "\" (metatype is \"" Chris@92: << metatype->tp_name << "\")" << endl; Chris@92: acceptable = false; Chris@92: break; Chris@92: } else { Chris@92: continue; Chris@92: } Chris@92: } Chris@92: Chris@92: if (metatype != &PyType_Type) { Chris@92: cerr << "ERROR: plugin " << classname Chris@92: << " uses Vampy reserved type name \"" << name Chris@92: << "\" for non-type (metatype is \"" Chris@92: << metatype->tp_name << "\")" << endl; Chris@92: acceptable = false; Chris@92: break; Chris@92: } Chris@92: Chris@92: PyTypeObject *type = (PyTypeObject *)item; Chris@92: if (type->tp_name == std::string("vampy.") + name) { Chris@92: DSTREAM << "Vampy: acceptable Vampy type name " Chris@92: << type->tp_name << " found in module" << endl; Chris@92: } else { Chris@92: cerr << "ERROR: plugin " << classname Chris@92: << " redefines Vampy type \"" << name << "\""; Chris@92: if (strcmp(type->tp_name, name)) { Chris@92: cerr << " (as \"" << type->tp_name << "\")"; Chris@92: } Chris@92: cerr << endl; Chris@92: acceptable = false; Chris@92: break; Chris@92: } Chris@92: } Chris@92: Chris@92: if (acceptable) { Chris@92: return pyClass; Chris@92: } else { Chris@92: PyObject *key = PyString_FromString(classname.c_str()); Chris@92: PyDict_SetItem(pyDict, key, Py_None); Chris@92: Py_DECREF(key); Chris@92: return NULL; Chris@92: } 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: 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: 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: std::vector path; fazekasgy@37: std::string envPath; fazekasgy@37: Chris@97: bool nonNative32 = false; Chris@97: #ifdef _WIN32 Chris@97: BOOL (WINAPI *fnIsWow64Process)(HANDLE, PBOOL) = Chris@97: (BOOL (WINAPI *)(HANDLE, PBOOL)) GetProcAddress Chris@97: (GetModuleHandle(TEXT("kernel32")), "IsWow64Process"); Chris@97: if (fnIsWow64Process) { Chris@97: BOOL wow64 = FALSE; Chris@97: if (fnIsWow64Process(GetCurrentProcess(), &wow64) && wow64) { Chris@97: nonNative32 = true; Chris@97: } Chris@97: } Chris@97: #endif Chris@97: Chris@97: char *cpath; Chris@97: if (nonNative32) { Chris@97: cpath = getenv("VAMP_PATH_32"); Chris@97: } else { Chris@97: cpath = getenv("VAMP_PATH"); Chris@97: } 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: }