Mercurial > hg > vampy
comparison PyPlugScanner.cpp @ 37:27bab3a16c9a vampy2final
new branch Vampy2final
author | fazekasgy |
---|---|
date | Mon, 05 Oct 2009 11:28:00 +0000 |
parents | |
children | 62dcaa5fe6f8 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 37:27bab3a16c9a |
---|---|
1 /* | |
2 | |
3 * Vampy : This plugin is a wrapper around the Vamp plugin API. | |
4 * It allows for writing Vamp plugins in Python. | |
5 | |
6 * Centre for Digital Music, Queen Mary University of London. | |
7 * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources | |
8 * for licence information.) | |
9 | |
10 */ | |
11 | |
12 | |
13 #include "PyPlugScanner.h" | |
14 #include <algorithm> | |
15 #include <cstdlib> | |
16 //#include "vamp-hostsdk/PluginHostAdapter.h" | |
17 | |
18 #ifdef _WIN32 | |
19 #include <windows.h> | |
20 #include <tchar.h> | |
21 #define pathsep ("\\") | |
22 #else | |
23 #include <dirent.h> | |
24 #include <dlfcn.h> | |
25 #define pathsep ("/") | |
26 #endif | |
27 #define joinPath(a,b) ( (a)+pathsep+(b) ) | |
28 | |
29 using std::string; | |
30 using std::vector; | |
31 using std::cerr; | |
32 using std::endl; | |
33 using std::find; | |
34 | |
35 PyPlugScanner::PyPlugScanner() | |
36 { | |
37 | |
38 } | |
39 | |
40 PyPlugScanner *PyPlugScanner::m_instance = NULL; | |
41 bool PyPlugScanner::m_hasInstance = false; | |
42 | |
43 PyPlugScanner* | |
44 PyPlugScanner::getInstance() | |
45 { | |
46 if (!m_hasInstance) { | |
47 m_instance = new PyPlugScanner(); | |
48 m_hasInstance = true; | |
49 } | |
50 return m_instance; | |
51 } | |
52 | |
53 void | |
54 PyPlugScanner::setPath(vector<string> path) | |
55 { | |
56 m_path=path; | |
57 } | |
58 | |
59 // We assume that each script on the path has one valid class | |
60 vector<string> | |
61 PyPlugScanner::getPyPlugs() | |
62 { | |
63 //for_each m_path listFiles then return vector<pyPlugs> | |
64 //key format: FullPathString/FileName.py:ClassName | |
65 | |
66 bool getCompiled = true; | |
67 char* getPyc = getenv("VAMPY_COMPILED"); | |
68 if (getPyc) { | |
69 string value(getPyc); | |
70 cerr << "VAMPY_COMPILED=" << value << endl; | |
71 getCompiled = value.compare("1")?false:true; | |
72 } | |
73 | |
74 vector<string> pyPlugs; | |
75 string pluginKey; | |
76 PyObject *pyClass; | |
77 | |
78 for (size_t i = 0; i < m_path.size(); ++i) { | |
79 | |
80 vector<string> files = listFiles(m_path[i],"py"); | |
81 | |
82 /// recognise byte compiled plugins | |
83 if (getCompiled) { | |
84 vector<string> compiled_files = listFiles(m_path[i],"pyc"); | |
85 mergeFileLists(compiled_files,files); | |
86 } | |
87 | |
88 for (vector<string>::iterator fi = files.begin(); | |
89 fi != files.end(); ++fi) { | |
90 string script = *fi; | |
91 if (!script.empty()) { | |
92 string classname=script.substr(0,script.rfind('.')); | |
93 pluginKey=joinPath(m_path[i],script)+":"+classname; | |
94 pyClass = getScriptClass(m_path[i],classname); | |
95 if (pyClass == NULL) | |
96 cerr << "Warning: Syntax error in VamPy plugin: " | |
97 << classname << ". Avoiding plugin." << endl; | |
98 else { | |
99 pyPlugs.push_back(pluginKey); | |
100 m_pyClasses.push_back(pyClass); | |
101 } | |
102 //pyPlugs.push_back(pluginKey); | |
103 } | |
104 } | |
105 } | |
106 | |
107 return pyPlugs; | |
108 | |
109 } | |
110 | |
111 /// insert python byte code names (.pyc) if a .py file can not be found | |
112 /// The interpreter automatically generates byte code files and executes | |
113 /// them if they exist. Therefore, we prefer .py files, but we allow | |
114 /// (relatively) closed source distributions by recognising .pyc files. | |
115 void | |
116 PyPlugScanner::mergeFileLists(vector<string> &pyc, vector<string> &py) | |
117 { | |
118 for (vector<string>::iterator pycit = pyc.begin(); | |
119 pycit != pyc.end(); ++pycit) { | |
120 // cerr << *pycit; | |
121 string pyc_name = *pycit; | |
122 string py_name = pyc_name.substr(0,pyc_name.rfind('.')) + ".py"; | |
123 vector<string>::iterator pyit = find (py.begin(), py.end(), py_name); | |
124 if (pyit == py.end()) py.push_back(pyc_name); | |
125 } | |
126 | |
127 } | |
128 | |
129 | |
130 //For now return one class object found in each script | |
131 vector<PyObject*> | |
132 PyPlugScanner::getPyClasses() | |
133 { | |
134 return m_pyClasses; | |
135 | |
136 } | |
137 | |
138 //Validate | |
139 //This should not be called more than once! | |
140 PyObject* | |
141 PyPlugScanner::getScriptClass(string path, string classname) | |
142 { | |
143 | |
144 //Add plugin path to active Python Path | |
145 string pyCmd = "import sys\nsys.path.append('" + path + "')\n"; | |
146 PyRun_SimpleString(pyCmd.c_str()); | |
147 | |
148 //Assign an object to the source code | |
149 PyObject *pySource = PyString_FromString(classname.c_str()); | |
150 | |
151 //Import it as a module into the py interpreter | |
152 PyObject *pyModule = PyImport_Import(pySource); | |
153 PyObject* pyError = PyErr_Occurred(); | |
154 if (! pyError == 0) { | |
155 cerr << "ERROR: error importing source: " << classname << endl; | |
156 PyErr_Print(); | |
157 Py_DECREF(pySource); | |
158 Py_CLEAR(pyModule); // safer if pyModule==NULL | |
159 return NULL; | |
160 } | |
161 Py_DECREF(pySource); | |
162 | |
163 //Read the dictionary object holding the namespace of the module (borrowed reference) | |
164 PyObject *pyDict = PyModule_GetDict(pyModule); | |
165 Py_DECREF(pyModule); | |
166 | |
167 //Get the PluginClass from the module (borrowed reference) | |
168 PyObject *pyClass = PyDict_GetItemString(pyDict, classname.c_str()); | |
169 | |
170 //Check if class is present and a callable method is implemented | |
171 if (pyClass && PyCallable_Check(pyClass)) { | |
172 | |
173 return pyClass; | |
174 } | |
175 else { | |
176 cerr << "ERROR: callable plugin class could not be found in source: " << classname << endl | |
177 << "Hint: plugin source filename and plugin class name must be the same." << endl; | |
178 PyErr_Print(); | |
179 return NULL; | |
180 } | |
181 } | |
182 | |
183 | |
184 | |
185 // Return a list of files in dir with given extension | |
186 // Code taken from hostext/PluginLoader.cpp | |
187 vector<string> | |
188 PyPlugScanner::listFiles(string dir, string extension) | |
189 { | |
190 vector<string> files; | |
191 | |
192 #ifdef _WIN32 | |
193 | |
194 string expression = dir + "\\*." + extension; | |
195 WIN32_FIND_DATA data; | |
196 HANDLE fh = FindFirstFile(expression.c_str(), &data); | |
197 if (fh == INVALID_HANDLE_VALUE) return files; | |
198 | |
199 bool ok = true; | |
200 while (ok) { | |
201 files.push_back(data.cFileName); | |
202 ok = FindNextFile(fh, &data); | |
203 } | |
204 | |
205 FindClose(fh); | |
206 | |
207 #else | |
208 | |
209 size_t extlen = extension.length(); | |
210 DIR *d = opendir(dir.c_str()); | |
211 if (!d) return files; | |
212 | |
213 struct dirent *e = 0; | |
214 while ((e = readdir(d))) { | |
215 | |
216 if (!e->d_name) continue; | |
217 | |
218 size_t len = strlen(e->d_name); | |
219 if (len < extlen + 2 || | |
220 e->d_name + len - extlen - 1 != "." + extension) { | |
221 continue; | |
222 } | |
223 //cerr << "pyscripts: " << e->d_name << endl; | |
224 files.push_back(e->d_name); | |
225 } | |
226 | |
227 closedir(d); | |
228 #endif | |
229 | |
230 return files; | |
231 } | |
232 | |
233 | |
234 //!!! It would probably be better to actually call | |
235 // PluginHostAdapter::getPluginPath. That would mean this "plugin" | |
236 // needs to link against vamp-hostsdk, but that's probably acceptable | |
237 // as it is sort of a host as well. | |
238 | |
239 // std::vector<std::string> | |
240 // PyPlugScanner::getAllValidPath() | |
241 // { | |
242 // Vamp::PluginHostAdapter host_adapter( ??? ); | |
243 // return host_adapter.getPluginPath(); | |
244 // } | |
245 | |
246 // tried to implement it, but found a bit confusing how to | |
247 // instantiate the host adapter here... | |
248 | |
249 | |
250 //Return correct plugin directories as per platform | |
251 //Code taken from vamp-sdk/PluginHostAdapter.cpp | |
252 std::vector<std::string> | |
253 PyPlugScanner::getAllValidPath() | |
254 { | |
255 | |
256 std::vector<std::string> path; | |
257 std::string envPath; | |
258 | |
259 char *cpath = getenv("VAMP_PATH"); | |
260 if (cpath) envPath = cpath; | |
261 | |
262 #ifdef _WIN32 | |
263 #define PATH_SEPARATOR ';' | |
264 #define DEFAULT_VAMP_PATH "%ProgramFiles%\\Vamp Plugins" | |
265 #else | |
266 #define PATH_SEPARATOR ':' | |
267 #ifdef __APPLE__ | |
268 #define DEFAULT_VAMP_PATH "$HOME/Library/Audio/Plug-Ins/Vamp:/Library/Audio/Plug-Ins/Vamp" | |
269 #else | |
270 #define DEFAULT_VAMP_PATH "$HOME/vamp:$HOME/.vamp:/usr/local/lib/vamp:/usr/lib/vamp" | |
271 #endif | |
272 #endif | |
273 | |
274 if (envPath == "") { | |
275 envPath = DEFAULT_VAMP_PATH; | |
276 char *chome = getenv("HOME"); | |
277 if (chome) { | |
278 std::string home(chome); | |
279 std::string::size_type f; | |
280 while ((f = envPath.find("$HOME")) != std::string::npos && | |
281 f < envPath.length()) { | |
282 envPath.replace(f, 5, home); | |
283 } | |
284 } | |
285 #ifdef _WIN32 | |
286 char *cpfiles = getenv("ProgramFiles"); | |
287 if (!cpfiles) cpfiles = "C:\\Program Files"; | |
288 std::string pfiles(cpfiles); | |
289 std::string::size_type f; | |
290 while ((f = envPath.find("%ProgramFiles%")) != std::string::npos && | |
291 f < envPath.length()) { | |
292 envPath.replace(f, 14, pfiles); | |
293 } | |
294 #endif | |
295 } | |
296 | |
297 std::string::size_type index = 0, newindex = 0; | |
298 | |
299 while ((newindex = envPath.find(PATH_SEPARATOR, index)) < envPath.size()) { | |
300 path.push_back(envPath.substr(index, newindex - index)); | |
301 index = newindex + 1; | |
302 } | |
303 | |
304 path.push_back(envPath.substr(index)); | |
305 | |
306 //can add an extra path for vampy plugins | |
307 char* extraPath = getenv("VAMPY_EXTPATH"); | |
308 if (extraPath) { | |
309 string vampyPath(extraPath); | |
310 cerr << "VAMPY_EXTPATH=" << vampyPath << endl; | |
311 path.push_back(vampyPath); | |
312 } | |
313 | |
314 return path; | |
315 } |