fazekasgy@0
|
1 /**
|
cannam@7
|
2 * This Vamp plugin is a wrapper for Python Scripts. (VamPy)
|
fazekasgy@0
|
3 * Centre for Digital Music, Queen Mary, University of London.
|
fazekasgy@0
|
4 * Copyright 2008, George Fazekas.
|
fazekasgy@0
|
5
|
fazekasgy@0
|
6 */
|
fazekasgy@0
|
7
|
fazekasgy@0
|
8
|
fazekasgy@0
|
9 #include "PyPlugScanner.h"
|
fazekasgy@0
|
10
|
fazekasgy@0
|
11 //#include <fstream>
|
fazekasgy@0
|
12 //#include <cctype>
|
fazekasgy@0
|
13
|
fazekasgy@0
|
14 #ifdef _WIN32
|
fazekasgy@0
|
15 #include <windows.h>
|
fazekasgy@0
|
16 #include <tchar.h>
|
fazekasgy@0
|
17 #define pathsep ("\\")
|
fazekasgy@0
|
18 #else
|
fazekasgy@0
|
19 #include <dirent.h>
|
fazekasgy@0
|
20 #include <dlfcn.h>
|
fazekasgy@0
|
21 #define pathsep ("/")
|
fazekasgy@0
|
22 #endif
|
fazekasgy@0
|
23 #define joinPath(a,b) ( (a)+pathsep+(b) )
|
fazekasgy@0
|
24
|
fazekasgy@0
|
25 using std::string;
|
fazekasgy@0
|
26 using std::vector;
|
fazekasgy@0
|
27 using std::cerr;
|
fazekasgy@0
|
28 using std::endl;
|
fazekasgy@0
|
29
|
fazekasgy@0
|
30 PyPlugScanner::PyPlugScanner()
|
fazekasgy@0
|
31 {
|
fazekasgy@0
|
32
|
fazekasgy@0
|
33 }
|
fazekasgy@0
|
34
|
fazekasgy@0
|
35 PyPlugScanner *PyPlugScanner::m_instance = NULL;
|
fazekasgy@0
|
36 bool PyPlugScanner::m_hasInstance = false;
|
fazekasgy@0
|
37
|
fazekasgy@0
|
38 PyPlugScanner*
|
fazekasgy@0
|
39 PyPlugScanner::getInstance()
|
fazekasgy@0
|
40 {
|
fazekasgy@0
|
41 if (!m_hasInstance) {
|
fazekasgy@0
|
42 m_instance = new PyPlugScanner();
|
fazekasgy@0
|
43 m_hasInstance = true;
|
fazekasgy@0
|
44 }
|
fazekasgy@0
|
45 return m_instance;
|
fazekasgy@0
|
46 }
|
fazekasgy@0
|
47
|
fazekasgy@0
|
48 void
|
fazekasgy@0
|
49 PyPlugScanner::setPath(vector<string> path)
|
fazekasgy@0
|
50 {
|
fazekasgy@0
|
51 m_path=path;
|
fazekasgy@0
|
52 }
|
fazekasgy@0
|
53
|
fazekasgy@0
|
54 // TODO: This should return all scripts for all valid paths
|
fazekasgy@0
|
55 // Validate python classes here?
|
fazekasgy@0
|
56 // For now, we assume that each script on the path has one valid class
|
fazekasgy@0
|
57 vector<string>
|
fazekasgy@0
|
58 PyPlugScanner::getPyPlugs()
|
fazekasgy@0
|
59 {
|
fazekasgy@0
|
60 //foreach m_path listFiles then return vector<pyPlugs>
|
fazekasgy@0
|
61 //format: FullPathString/FileName.py:ClassName
|
fazekasgy@0
|
62
|
fazekasgy@0
|
63 vector<string> pyPlugs;
|
fazekasgy@0
|
64 string pluginKey;
|
fazekasgy@0
|
65 PyObject *pyClassInstance;
|
fazekasgy@0
|
66
|
fazekasgy@0
|
67 for (size_t i = 0; i < m_path.size(); ++i) {
|
fazekasgy@0
|
68
|
fazekasgy@0
|
69 vector<string> files = listFiles(m_path[i],"py");
|
fazekasgy@0
|
70
|
fazekasgy@0
|
71 for (vector<string>::iterator fi = files.begin();
|
fazekasgy@0
|
72 fi != files.end(); ++fi) {
|
fazekasgy@0
|
73 string script = *fi;
|
fazekasgy@0
|
74 if (!script.empty()) {
|
fazekasgy@0
|
75 string classname=script.substr(0,script.rfind('.'));
|
fazekasgy@0
|
76 pluginKey=joinPath(m_path[i],script)+":"+classname;
|
fazekasgy@0
|
77 pyClassInstance = getScriptInstance(m_path[i],classname);
|
fazekasgy@0
|
78 if (pyClassInstance == NULL)
|
fazekasgy@0
|
79 cerr << "Warning: Syntax error in VamPy plugin: "
|
fazekasgy@0
|
80 << classname << ". Avoiding plugin." << endl;
|
fazekasgy@0
|
81 else {
|
fazekasgy@0
|
82 pyPlugs.push_back(pluginKey);
|
fazekasgy@0
|
83 m_pyInstances.push_back(pyClassInstance);
|
fazekasgy@0
|
84 }
|
fazekasgy@0
|
85 //pyPlugs.push_back(pluginKey);
|
fazekasgy@0
|
86 }
|
fazekasgy@0
|
87 }
|
fazekasgy@0
|
88 }
|
fazekasgy@0
|
89
|
fazekasgy@0
|
90 return pyPlugs;
|
fazekasgy@0
|
91
|
fazekasgy@0
|
92 }
|
fazekasgy@0
|
93
|
fazekasgy@0
|
94
|
fazekasgy@0
|
95 //For now return one class instance found in each script
|
fazekasgy@0
|
96 vector<PyObject*>
|
fazekasgy@0
|
97 PyPlugScanner::getPyInstances()
|
fazekasgy@0
|
98 {
|
fazekasgy@0
|
99 return m_pyInstances;
|
fazekasgy@0
|
100
|
fazekasgy@0
|
101 }
|
fazekasgy@0
|
102
|
fazekasgy@0
|
103
|
fazekasgy@0
|
104 //Validate
|
fazekasgy@0
|
105 //This should not be called more than once!
|
fazekasgy@0
|
106 PyObject*
|
fazekasgy@0
|
107 PyPlugScanner::getScriptInstance(string path, string classname)
|
fazekasgy@0
|
108 {
|
fazekasgy@0
|
109
|
fazekasgy@0
|
110 //Add plugin path to active Python Path
|
fazekasgy@0
|
111 string pyCmd = "import sys\nsys.path.append('" + path + "')\n";
|
fazekasgy@0
|
112 PyRun_SimpleString(pyCmd.c_str());
|
fazekasgy@0
|
113
|
fazekasgy@0
|
114 //Assign an object to the source code
|
fazekasgy@0
|
115 PyObject *pySource = PyString_FromString(classname.c_str());
|
fazekasgy@0
|
116
|
fazekasgy@0
|
117 //Import it as a module into the py interpreter
|
fazekasgy@0
|
118 PyObject *pyModule = PyImport_Import(pySource);
|
fazekasgy@0
|
119 PyObject* pyError = PyErr_Occurred();
|
fazekasgy@0
|
120 if (! pyError == 0) {
|
fazekasgy@0
|
121 cerr << "ERROR: error importing source: " << classname << endl;
|
fazekasgy@0
|
122 PyErr_Print();
|
fazekasgy@0
|
123 Py_DECREF(pySource);
|
fazekasgy@0
|
124 Py_CLEAR(pyModule); // safer if pyModule==NULL
|
fazekasgy@0
|
125 return NULL;
|
fazekasgy@0
|
126 }
|
fazekasgy@0
|
127 Py_DECREF(pySource);
|
fazekasgy@0
|
128
|
fazekasgy@0
|
129 //Read the namespace of the module into a dictionary object (borrowed reference)
|
fazekasgy@0
|
130 PyObject *pyDict = PyModule_GetDict(pyModule);
|
fazekasgy@0
|
131 Py_DECREF(pyModule);
|
fazekasgy@0
|
132
|
fazekasgy@0
|
133 //Get the PluginClass from the module (borrowed reference)
|
fazekasgy@0
|
134 PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str());
|
fazekasgy@0
|
135
|
fazekasgy@0
|
136 //Check if class is present and a callable method is implemented
|
fazekasgy@0
|
137 if (pyClass && PyCallable_Check(pyClass)) {
|
fazekasgy@0
|
138
|
fazekasgy@0
|
139 //Create an instance
|
fazekasgy@0
|
140 PyObject *pyInstance = PyObject_CallObject(pyClass, NULL);
|
fazekasgy@0
|
141 //cerr << "__(getInstance) PyPlugin Class: " << m_class << " successfully created.__" << endl;
|
fazekasgy@0
|
142 return pyInstance;
|
fazekasgy@0
|
143 } else return NULL;
|
fazekasgy@0
|
144 }
|
fazekasgy@0
|
145
|
fazekasgy@0
|
146
|
fazekasgy@0
|
147
|
fazekasgy@0
|
148 // Return a list of files in dir with given extension
|
fazekasgy@0
|
149 // Code taken from hostext/PluginLoader.cpp
|
fazekasgy@0
|
150 vector<string>
|
fazekasgy@0
|
151 PyPlugScanner::listFiles(string dir, string extension)
|
fazekasgy@0
|
152 {
|
fazekasgy@0
|
153 vector<string> files;
|
fazekasgy@0
|
154
|
fazekasgy@0
|
155 #ifdef _WIN32
|
fazekasgy@0
|
156
|
fazekasgy@0
|
157 string expression = dir + "\\*." + extension;
|
fazekasgy@0
|
158 WIN32_FIND_DATA data;
|
fazekasgy@0
|
159 HANDLE fh = FindFirstFile(expression.c_str(), &data);
|
fazekasgy@0
|
160 if (fh == INVALID_HANDLE_VALUE) return files;
|
fazekasgy@0
|
161
|
fazekasgy@0
|
162 bool ok = true;
|
fazekasgy@0
|
163 while (ok) {
|
fazekasgy@0
|
164 files.push_back(data.cFileName);
|
fazekasgy@0
|
165 ok = FindNextFile(fh, &data);
|
fazekasgy@0
|
166 }
|
fazekasgy@0
|
167
|
fazekasgy@0
|
168 FindClose(fh);
|
fazekasgy@0
|
169
|
fazekasgy@0
|
170 #else
|
fazekasgy@0
|
171
|
fazekasgy@0
|
172 size_t extlen = extension.length();
|
fazekasgy@0
|
173 DIR *d = opendir(dir.c_str());
|
fazekasgy@0
|
174 if (!d) return files;
|
fazekasgy@0
|
175
|
fazekasgy@0
|
176 struct dirent *e = 0;
|
fazekasgy@0
|
177 while ((e = readdir(d))) {
|
fazekasgy@0
|
178
|
fazekasgy@0
|
179 if (!(e->d_type & DT_REG) && (e->d_type != DT_UNKNOWN)) continue;
|
fazekasgy@0
|
180
|
fazekasgy@0
|
181 if (!e->d_name) continue;
|
fazekasgy@0
|
182
|
fazekasgy@0
|
183 size_t len = strlen(e->d_name);
|
fazekasgy@0
|
184 if (len < extlen + 2 ||
|
fazekasgy@0
|
185 e->d_name + len - extlen - 1 != "." + extension) {
|
fazekasgy@0
|
186 continue;
|
fazekasgy@0
|
187 }
|
fazekasgy@0
|
188 //cerr << "pyscripts: " << e->d_name << endl;
|
fazekasgy@0
|
189 files.push_back(e->d_name);
|
fazekasgy@0
|
190 }
|
fazekasgy@0
|
191
|
fazekasgy@0
|
192 closedir(d);
|
fazekasgy@0
|
193 #endif
|
fazekasgy@0
|
194
|
fazekasgy@0
|
195 return files;
|
fazekasgy@0
|
196 }
|
fazekasgy@0
|
197
|
fazekasgy@0
|
198 //Return correct plugin directories as per platform
|
fazekasgy@0
|
199 //Code taken from vamp-sdk/PluginHostAdapter.cpp
|
cannam@3
|
200
|
cannam@3
|
201 //!!! It would probably be better to actually call
|
cannam@3
|
202 // PluginHostAdapter::getPluginPath. That would mean this "plugin"
|
cannam@3
|
203 // needs to link against vamp-hostsdk, but that's probably acceptable
|
cannam@3
|
204 // as it is sort of a host as well.
|
cannam@3
|
205
|
fazekasgy@0
|
206 std::vector<std::string>
|
fazekasgy@0
|
207 PyPlugScanner::getAllValidPath()
|
fazekasgy@0
|
208 {
|
fazekasgy@0
|
209 std::vector<std::string> path;
|
fazekasgy@0
|
210 std::string envPath;
|
fazekasgy@0
|
211
|
fazekasgy@0
|
212 char *cpath = getenv("VAMP_PATH");
|
fazekasgy@0
|
213 if (cpath) envPath = cpath;
|
fazekasgy@0
|
214
|
fazekasgy@0
|
215 #ifdef _WIN32
|
fazekasgy@0
|
216 #define PATH_SEPARATOR ';'
|
fazekasgy@0
|
217 #define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins"
|
fazekasgy@0
|
218 #else
|
fazekasgy@0
|
219 #define PATH_SEPARATOR ':'
|
fazekasgy@0
|
220 #ifdef __APPLE__
|
fazekasgy@0
|
221 #define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp"
|
fazekasgy@0
|
222 #else
|
fazekasgy@0
|
223 #define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp"
|
fazekasgy@0
|
224 #endif
|
fazekasgy@0
|
225 #endif
|
fazekasgy@0
|
226
|
fazekasgy@0
|
227 if (envPath == "") {
|
fazekasgy@0
|
228 envPath = DEFAULT_VAMP_PATH;
|
fazekasgy@0
|
229 char *chome = getenv("HOME");
|
fazekasgy@0
|
230 if (chome) {
|
fazekasgy@0
|
231 std::string home(chome);
|
fazekasgy@0
|
232 std::string::size_type f;
|
fazekasgy@0
|
233 while ((f = envPath.find("$HOME")) != std::string::npos &&
|
fazekasgy@0
|
234 f < envPath.length()) {
|
fazekasgy@0
|
235 envPath.replace(f, 5, home);
|
fazekasgy@0
|
236 }
|
fazekasgy@0
|
237 }
|
fazekasgy@0
|
238 #ifdef _WIN32
|
fazekasgy@0
|
239 char *cpfiles = getenv("ProgramFiles");
|
fazekasgy@0
|
240 if (!cpfiles) cpfiles = "C:\\Program Files";
|
fazekasgy@0
|
241 std::string pfiles(cpfiles);
|
fazekasgy@0
|
242 std::string::size_type f;
|
fazekasgy@0
|
243 while ((f = envPath.find("%ProgramFiles%")) != std::string::npos &&
|
fazekasgy@0
|
244 f < envPath.length()) {
|
fazekasgy@0
|
245 envPath.replace(f, 14, pfiles);
|
fazekasgy@0
|
246 }
|
fazekasgy@0
|
247 #endif
|
fazekasgy@0
|
248 }
|
fazekasgy@0
|
249
|
fazekasgy@0
|
250 std::string::size_type index = 0, newindex = 0;
|
fazekasgy@0
|
251
|
fazekasgy@0
|
252 while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) {
|
fazekasgy@0
|
253 path.push_back(envPath.substr(index, newindex - index));
|
fazekasgy@0
|
254 index = newindex + 1;
|
fazekasgy@0
|
255 }
|
fazekasgy@0
|
256
|
fazekasgy@0
|
257 path.push_back(envPath.substr(index));
|
fazekasgy@0
|
258
|
fazekasgy@0
|
259 return path;
|
fazekasgy@0
|
260 }
|