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@37
|
84 vector<string> compiled_files = listFiles(m_path[i],"pyc");
|
fazekasgy@37
|
85 mergeFileLists(compiled_files,files);
|
fazekasgy@37
|
86 }
|
fazekasgy@37
|
87
|
fazekasgy@37
|
88 for (vector<string>::iterator fi = files.begin();
|
fazekasgy@37
|
89 fi != files.end(); ++fi) {
|
fazekasgy@37
|
90 string script = *fi;
|
fazekasgy@37
|
91 if (!script.empty()) {
|
fazekasgy@37
|
92 string classname=script.substr(0,script.rfind('.'));
|
fazekasgy@37
|
93 pluginKey=joinPath(m_path[i],script)+":"+classname;
|
fazekasgy@37
|
94 pyClass = getScriptClass(m_path[i],classname);
|
fazekasgy@37
|
95 if (pyClass == NULL)
|
fazekasgy@37
|
96 cerr << "Warning: Syntax error in VamPy plugin: "
|
fazekasgy@37
|
97 << classname << ". Avoiding plugin." << endl;
|
fazekasgy@37
|
98 else {
|
fazekasgy@37
|
99 pyPlugs.push_back(pluginKey);
|
fazekasgy@37
|
100 m_pyClasses.push_back(pyClass);
|
fazekasgy@37
|
101 }
|
fazekasgy@37
|
102 //pyPlugs.push_back(pluginKey);
|
fazekasgy@37
|
103 }
|
fazekasgy@37
|
104 }
|
fazekasgy@37
|
105 }
|
fazekasgy@37
|
106
|
fazekasgy@37
|
107 return pyPlugs;
|
fazekasgy@37
|
108
|
fazekasgy@37
|
109 }
|
fazekasgy@37
|
110
|
fazekasgy@37
|
111 /// insert python byte code names (.pyc) if a .py file can not be found
|
fazekasgy@37
|
112 /// The interpreter automatically generates byte code files and executes
|
fazekasgy@37
|
113 /// them if they exist. Therefore, we prefer .py files, but we allow
|
fazekasgy@37
|
114 /// (relatively) closed source distributions by recognising .pyc files.
|
fazekasgy@37
|
115 void
|
fazekasgy@37
|
116 PyPlugScanner::mergeFileLists(vector<string> &pyc, vector<string> &py)
|
fazekasgy@37
|
117 {
|
fazekasgy@37
|
118 for (vector<string>::iterator pycit = pyc.begin();
|
fazekasgy@37
|
119 pycit != pyc.end(); ++pycit) {
|
fazekasgy@37
|
120 // cerr << *pycit;
|
fazekasgy@37
|
121 string pyc_name = *pycit;
|
fazekasgy@37
|
122 string py_name = pyc_name.substr(0,pyc_name.rfind('.')) + ".py";
|
fazekasgy@37
|
123 vector<string>::iterator pyit = find (py.begin(), py.end(), py_name);
|
fazekasgy@37
|
124 if (pyit == py.end()) py.push_back(pyc_name);
|
fazekasgy@37
|
125 }
|
fazekasgy@37
|
126
|
fazekasgy@37
|
127 }
|
fazekasgy@37
|
128
|
fazekasgy@37
|
129
|
fazekasgy@37
|
130 //For now return one class object found in each script
|
fazekasgy@37
|
131 vector<PyObject*>
|
fazekasgy@37
|
132 PyPlugScanner::getPyClasses()
|
fazekasgy@37
|
133 {
|
fazekasgy@37
|
134 return m_pyClasses;
|
fazekasgy@37
|
135
|
fazekasgy@37
|
136 }
|
fazekasgy@37
|
137
|
fazekasgy@37
|
138 //Validate
|
fazekasgy@37
|
139 //This should not be called more than once!
|
fazekasgy@37
|
140 PyObject*
|
fazekasgy@37
|
141 PyPlugScanner::getScriptClass(string path, string classname)
|
fazekasgy@37
|
142 {
|
fazekasgy@37
|
143
|
fazekasgy@37
|
144 //Add plugin path to active Python Path
|
fazekasgy@37
|
145 string pyCmd = "import sys\nsys.path.append('" + path + "')\n";
|
fazekasgy@37
|
146 PyRun_SimpleString(pyCmd.c_str());
|
fazekasgy@37
|
147
|
fazekasgy@37
|
148 //Assign an object to the source code
|
fazekasgy@37
|
149 PyObject *pySource = PyString_FromString(classname.c_str());
|
fazekasgy@37
|
150
|
fazekasgy@37
|
151 //Import it as a module into the py interpreter
|
fazekasgy@37
|
152 PyObject *pyModule = PyImport_Import(pySource);
|
fazekasgy@37
|
153 PyObject* pyError = PyErr_Occurred();
|
fazekasgy@37
|
154 if (! pyError == 0) {
|
fazekasgy@37
|
155 cerr << "ERROR: error importing source: " << classname << endl;
|
fazekasgy@37
|
156 PyErr_Print();
|
fazekasgy@37
|
157 Py_DECREF(pySource);
|
fazekasgy@37
|
158 Py_CLEAR(pyModule); // safer if pyModule==NULL
|
fazekasgy@37
|
159 return NULL;
|
fazekasgy@37
|
160 }
|
fazekasgy@37
|
161 Py_DECREF(pySource);
|
fazekasgy@37
|
162
|
fazekasgy@37
|
163 //Read the dictionary object holding the namespace of the module (borrowed reference)
|
fazekasgy@37
|
164 PyObject *pyDict = PyModule_GetDict(pyModule);
|
fazekasgy@37
|
165 Py_DECREF(pyModule);
|
fazekasgy@37
|
166
|
fazekasgy@37
|
167 //Get the PluginClass from the module (borrowed reference)
|
fazekasgy@37
|
168 PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str());
|
fazekasgy@37
|
169
|
fazekasgy@37
|
170 //Check if class is present and a callable method is implemented
|
fazekasgy@37
|
171 if (pyClass && PyCallable_Check(pyClass)) {
|
fazekasgy@37
|
172
|
fazekasgy@37
|
173 return pyClass;
|
fazekasgy@37
|
174 }
|
fazekasgy@37
|
175 else {
|
fazekasgy@37
|
176 cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl
|
fazekasgy@37
|
177 << "Hint: plugin source filename and plugin class name must be the same." << endl;
|
fazekasgy@37
|
178 PyErr_Print();
|
fazekasgy@37
|
179 return NULL;
|
fazekasgy@37
|
180 }
|
fazekasgy@37
|
181 }
|
fazekasgy@37
|
182
|
fazekasgy@37
|
183
|
fazekasgy@37
|
184
|
fazekasgy@37
|
185 // Return a list of files in dir with given extension
|
fazekasgy@37
|
186 // Code taken from hostext/PluginLoader.cpp
|
fazekasgy@37
|
187 vector<string>
|
fazekasgy@37
|
188 PyPlugScanner::listFiles(string dir, string extension)
|
fazekasgy@37
|
189 {
|
fazekasgy@37
|
190 vector<string> files;
|
fazekasgy@37
|
191
|
fazekasgy@37
|
192 #ifdef _WIN32
|
fazekasgy@37
|
193
|
fazekasgy@37
|
194 string expression = dir + "\\*." + extension;
|
fazekasgy@37
|
195 WIN32_FIND_DATA data;
|
fazekasgy@37
|
196 HANDLE fh = FindFirstFile(expression.c_str(), &data);
|
fazekasgy@37
|
197 if (fh == INVALID_HANDLE_VALUE) return files;
|
fazekasgy@37
|
198
|
fazekasgy@37
|
199 bool ok = true;
|
fazekasgy@37
|
200 while (ok) {
|
fazekasgy@37
|
201 files.push_back(data.cFileName);
|
fazekasgy@37
|
202 ok = FindNextFile(fh, &data);
|
fazekasgy@37
|
203 }
|
fazekasgy@37
|
204
|
fazekasgy@37
|
205 FindClose(fh);
|
fazekasgy@37
|
206
|
fazekasgy@37
|
207 #else
|
fazekasgy@37
|
208
|
fazekasgy@37
|
209 size_t extlen = extension.length();
|
fazekasgy@37
|
210 DIR *d = opendir(dir.c_str());
|
fazekasgy@37
|
211 if (!d) return files;
|
fazekasgy@37
|
212
|
fazekasgy@37
|
213 struct dirent *e = 0;
|
fazekasgy@37
|
214 while ((e = readdir(d))) {
|
fazekasgy@37
|
215
|
fazekasgy@37
|
216 if (!e->d_name) continue;
|
fazekasgy@37
|
217
|
fazekasgy@37
|
218 size_t len = strlen(e->d_name);
|
fazekasgy@37
|
219 if (len < extlen + 2 ||
|
fazekasgy@37
|
220 e->d_name + len - extlen - 1 != "." + extension) {
|
fazekasgy@37
|
221 continue;
|
fazekasgy@37
|
222 }
|
fazekasgy@37
|
223 //cerr << "pyscripts: " << e->d_name << endl;
|
fazekasgy@37
|
224 files.push_back(e->d_name);
|
fazekasgy@37
|
225 }
|
fazekasgy@37
|
226
|
fazekasgy@37
|
227 closedir(d);
|
fazekasgy@37
|
228 #endif
|
fazekasgy@37
|
229
|
fazekasgy@37
|
230 return files;
|
fazekasgy@37
|
231 }
|
fazekasgy@37
|
232
|
fazekasgy@37
|
233
|
fazekasgy@37
|
234 //!!! It would probably be better to actually call
|
fazekasgy@37
|
235 // PluginHostAdapter::getPluginPath. That would mean this "plugin"
|
fazekasgy@37
|
236 // needs to link against vamp-hostsdk, but that's probably acceptable
|
fazekasgy@37
|
237 // as it is sort of a host as well.
|
fazekasgy@37
|
238
|
fazekasgy@37
|
239 // std::vector<std::string>
|
fazekasgy@37
|
240 // PyPlugScanner::getAllValidPath()
|
fazekasgy@37
|
241 // {
|
fazekasgy@37
|
242 // Vamp::PluginHostAdapter host_adapter( ??? );
|
fazekasgy@37
|
243 // return host_adapter.getPluginPath();
|
fazekasgy@37
|
244 // }
|
fazekasgy@37
|
245
|
fazekasgy@37
|
246 // tried to implement it, but found a bit confusing how to
|
fazekasgy@37
|
247 // instantiate the host adapter here...
|
fazekasgy@37
|
248
|
fazekasgy@37
|
249
|
fazekasgy@37
|
250 //Return correct plugin directories as per platform
|
fazekasgy@37
|
251 //Code taken from vamp-sdk/PluginHostAdapter.cpp
|
fazekasgy@37
|
252 std::vector<std::string>
|
fazekasgy@37
|
253 PyPlugScanner::getAllValidPath()
|
fazekasgy@37
|
254 {
|
fazekasgy@37
|
255
|
fazekasgy@37
|
256 std::vector<std::string> path;
|
fazekasgy@37
|
257 std::string envPath;
|
fazekasgy@37
|
258
|
fazekasgy@37
|
259 char *cpath = getenv("VAMP_PATH");
|
fazekasgy@37
|
260 if (cpath) envPath = cpath;
|
fazekasgy@37
|
261
|
fazekasgy@37
|
262 #ifdef _WIN32
|
fazekasgy@37
|
263 #define PATH_SEPARATOR ';'
|
fazekasgy@37
|
264 #define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins"
|
fazekasgy@37
|
265 #else
|
fazekasgy@37
|
266 #define PATH_SEPARATOR ':'
|
fazekasgy@37
|
267 #ifdef __APPLE__
|
fazekasgy@37
|
268 #define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp"
|
fazekasgy@37
|
269 #else
|
fazekasgy@37
|
270 #define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp"
|
fazekasgy@37
|
271 #endif
|
fazekasgy@37
|
272 #endif
|
fazekasgy@37
|
273
|
fazekasgy@37
|
274 if (envPath == "") {
|
fazekasgy@37
|
275 envPath = DEFAULT_VAMP_PATH;
|
fazekasgy@37
|
276 char *chome = getenv("HOME");
|
fazekasgy@37
|
277 if (chome) {
|
fazekasgy@37
|
278 std::string home(chome);
|
fazekasgy@37
|
279 std::string::size_type f;
|
fazekasgy@37
|
280 while ((f = envPath.find("$HOME")) != std::string::npos &&
|
fazekasgy@37
|
281 f < envPath.length()) {
|
fazekasgy@37
|
282 envPath.replace(f, 5, home);
|
fazekasgy@37
|
283 }
|
fazekasgy@37
|
284 }
|
fazekasgy@37
|
285 #ifdef _WIN32
|
fazekasgy@37
|
286 char *cpfiles = getenv("ProgramFiles");
|
fazekasgy@37
|
287 if (!cpfiles) cpfiles = "C:\\Program Files";
|
fazekasgy@37
|
288 std::string pfiles(cpfiles);
|
fazekasgy@37
|
289 std::string::size_type f;
|
fazekasgy@37
|
290 while ((f = envPath.find("%ProgramFiles%")) != std::string::npos &&
|
fazekasgy@37
|
291 f < envPath.length()) {
|
fazekasgy@37
|
292 envPath.replace(f, 14, pfiles);
|
fazekasgy@37
|
293 }
|
fazekasgy@37
|
294 #endif
|
fazekasgy@37
|
295 }
|
fazekasgy@37
|
296
|
fazekasgy@37
|
297 std::string::size_type index = 0, newindex = 0;
|
fazekasgy@37
|
298
|
fazekasgy@37
|
299 while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) {
|
fazekasgy@37
|
300 path.push_back(envPath.substr(index, newindex - index));
|
fazekasgy@37
|
301 index = newindex + 1;
|
fazekasgy@37
|
302 }
|
fazekasgy@37
|
303
|
fazekasgy@37
|
304 path.push_back(envPath.substr(index));
|
fazekasgy@37
|
305
|
fazekasgy@37
|
306 //can add an extra path for vampy plugins
|
fazekasgy@37
|
307 char* extraPath = getenv("VAMPY_EXTPATH");
|
fazekasgy@37
|
308 if (extraPath) {
|
fazekasgy@37
|
309 string vampyPath(extraPath);
|
fazekasgy@37
|
310 cerr << "VAMPY_EXTPATH=" << vampyPath << endl;
|
fazekasgy@37
|
311 path.push_back(vampyPath);
|
fazekasgy@37
|
312 }
|
fazekasgy@37
|
313
|
fazekasgy@37
|
314 return path;
|
fazekasgy@37
|
315 }
|