+ Filterclavier: make non-experimental
[calf.git] / bigbull / lv2.py
blob5bc3ffde78a2e0c0678fa29ac2cf44606ff3bc53
1 import re
2 import os
3 import sys
4 import glob
5 import calfpytools
7 lv2 = "http://lv2plug.in/ns/lv2core#"
8 lv2evt = "http://lv2plug.in/ns/ext/event#"
9 rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
10 rdfs = "http://www.w3.org/2000/01/rdf-schema#"
11 epi = "http://lv2plug.in/ns/dev/extportinfo#"
12 rdf_type = rdf + "type"
13 tinyname_uri = "http://lv2plug.in/ns/dev/tiny-name"
15 class DumpRDFModel:
16 def addTriple(self, s, p, o):
17 print "%s [%s] %s" % (s, p, repr(o))
19 class SimpleRDFModel:
20 def __init__(self):
21 self.bySubject = {}
22 self.byPredicate = {}
23 def getByType(self, classname):
24 classes = self.bySubject["$classes"]
25 if classname in classes:
26 return classes[classname]
27 return []
28 def getByPropType(self, propname):
29 if propname in self.byPredicate:
30 return self.byPredicate[propname]
31 return []
32 def getProperty(self, subject, props, optional = False, single = False):
33 if type(props) is list:
34 prop = props[0]
35 else:
36 prop = props
37 if type(subject) is str:
38 subject = self.bySubject[subject]
39 elif type(subject) is dict:
40 pass
41 else:
42 if single:
43 return None
44 else:
45 return []
46 anyprops = set()
47 if prop in subject:
48 for o in subject[prop]:
49 anyprops.add(o)
50 if type(props) is list:
51 if len(props) > 1:
52 result = set()
53 for v in anyprops:
54 if single:
55 value = self.getProperty(v, props[1:], optional = optional, single = True)
56 if value != None:
57 return value
58 else:
59 result |= set(self.getProperty(v, props[1:], optional = optional, single = False))
60 if single:
61 return None
62 else:
63 return list(result)
64 if single:
65 if len(anyprops) > 0:
66 if len(anyprops) > 1:
67 raise Exception, "More than one value of " + prop
68 return list(anyprops)[0]
69 else:
70 return None
71 return list(anyprops)
74 def addTriple(self, s, p, o):
75 if p == rdf_type:
76 p = "a"
77 if s not in self.bySubject:
78 self.bySubject[s] = {}
79 if p not in self.bySubject[s]:
80 self.bySubject[s][p] = []
81 self.bySubject[s][p].append(o)
82 if p not in self.byPredicate:
83 self.byPredicate[p] = {}
84 if s not in self.byPredicate[p]:
85 self.byPredicate[p][s] = []
86 self.byPredicate[p][s].append(o)
87 if p == "a":
88 self.addTriple("$classes", o, s)
89 def copyFrom(self, src):
90 for s in src.bySubject:
91 po = src.bySubject[s]
92 for p in po:
93 for o in po[p]:
94 self.addTriple(s, p, o)
95 def dump(self):
96 for s in self.bySubject.keys():
97 for p in self.bySubject[s].keys():
98 print "%s %s %s" % (s, p, self.bySubject[s][p])
100 def parseTTL(uri, content, model, debug):
101 # Missing stuff: translated literals, blank nodes
102 if debug:
103 print "Parsing: %s" % uri
104 prefixes = {}
105 spo_stack = []
106 spo = ["", "", ""]
107 item = 0
108 anoncnt = 1
109 for x in calfpytools.scan_ttl_string(content):
110 if x[0] == '':
111 continue
112 if x[0] == 'prefix':
113 spo[0] = "@prefix"
114 item = 1
115 continue
116 elif (x[0] == '.' and spo_stack == []) or x[0] == ';' or x[0] == ',':
117 if item == 3:
118 if spo[0] == "@prefix":
119 prefixes[spo[1][:-1]] = spo[2]
120 else:
121 model.addTriple(spo[0], spo[1], spo[2])
122 if x[0] == '.': item = 0
123 elif x[0] == ';': item = 1
124 elif x[0] == ',': item = 2
125 else:
126 if x[0] == '.':
127 item = 0
128 elif item != 0:
129 raise Exception, uri+": Unexpected " + x[0]
130 elif x[0] == "prnot" and item < 3:
131 prnot = x[1].split(":")
132 if item != 0 and spo[0] == "@prefix":
133 spo[item] = x[1]
134 elif prnot[0] == "_":
135 spo[item] = uri + "#" + prnot[1]
136 else:
137 spo[item] = prefixes[prnot[0]] + prnot[1]
138 item += 1
139 elif (x[0] == 'URI' or x[0] == "string" or x[0] == "number" or (x[0] == "symbol" and x[1] == "a" and item == 1)) and (item < 3):
140 if x[0] == "URI" and x[1] == "":
141 x = ("URI", uri)
142 elif x[0] == "URI" and x[1].find(":") == -1 and x[1] != "" and x[1][0] != "/":
143 # This is quite silly
144 x = ("URI", os.path.dirname(uri) + "/" + x[1])
145 spo[item] = x[1]
146 item += 1
147 elif x[0] == '[':
148 if item != 2:
149 raise Exception, "Incorrect use of ["
150 uri2 = uri + "$anon$" + str(anoncnt)
151 spo[2] = uri2
152 spo_stack.append(spo)
153 spo = [uri2, "", ""]
154 item = 1
155 anoncnt += 1
156 elif x[0] == ']' or x[0] == ')':
157 if item == 3:
158 model.addTriple(spo[0], spo[1], spo[2])
159 item = 0
160 spo = spo_stack[-1]
161 spo_stack = spo_stack[:-1]
162 item = 3
163 elif x[0] == '(':
164 if item != 2:
165 raise Exception, "Incorrect use of ("
166 uri2 = uri + "$anon$" + str(anoncnt)
167 spo[2] = uri2
168 spo_stack.append(spo)
169 spo = [uri2, "", ""]
170 item = 2
171 anoncnt += 1
172 else:
173 print uri + ": Unexpected: " + repr(x)
175 class LV2Port(object):
176 def __init__(self):
177 pass
178 def connectableTo(self, port):
179 if not ((self.isInput and port.isOutput) or (self.isOutput and port.isInput)):
180 return False
181 if self.isAudio != port.isAudio or self.isControl != port.isControl or self.isEvent != port.isEvent:
182 return False
183 if not self.isAudio and not self.isControl and not self.isEvent:
184 return False
185 return True
187 class LV2Plugin(object):
188 def __init__(self):
189 pass
191 class LV2DB:
192 def __init__(self, debug = False):
193 self.debug = debug
194 self.initManifests()
196 def initManifests(self):
197 lv2path = ["/usr/lib/lv2", "/usr/local/lib/lv2"]
198 self.manifests = SimpleRDFModel()
199 self.paths = {}
200 self.plugin_info = dict()
201 # Scan manifests
202 for dir in lv2path:
203 for bundle in glob.iglob(dir + "/*.lv2"):
204 fn = bundle+"/manifest.ttl"
205 if os.path.exists(fn):
206 parseTTL(fn, file(fn).read(), self.manifests, self.debug)
207 # Read all specifications from all manifests
208 if (lv2 + "Specification" in self.manifests.bySubject["$classes"]):
209 specs = self.manifests.getByType(lv2 + "Specification")
210 filenames = set()
211 for spec in specs:
212 subj = self.manifests.bySubject[spec]
213 if rdfs+"seeAlso" in subj:
214 for fn in subj[rdfs+"seeAlso"]:
215 filenames.add(fn)
216 for fn in filenames:
217 parseTTL(fn, file(fn).read(), self.manifests, self.debug)
218 #fn = "/usr/lib/lv2/lv2core.lv2/lv2.ttl"
219 #parseTTL(fn, file(fn).read(), self.manifests)
220 self.plugins = self.manifests.getByType(lv2 + "Plugin")
221 self.categories = set()
222 self.category_paths = []
223 self.add_category_recursive([], lv2 + "Plugin")
225 def add_category_recursive(self, tree_pos, category):
226 cat_name = self.manifests.getProperty(category, rdfs + "label", single = True, optional = True)
227 self.category_paths.append(((tree_pos + [cat_name])[1:], category))
228 self.categories.add(category)
229 items = self.manifests.byPredicate[rdfs + "subClassOf"]
230 for subj in items:
231 if subj in self.categories:
232 continue
233 for o in items[subj]:
234 if o == category and subj not in self.categories:
235 self.add_category_recursive(list(tree_pos) + [cat_name], subj)
237 def get_categories(self):
238 return self.category_paths
240 def getPluginList(self):
241 return self.plugins
243 def getPluginInfo(self, uri):
244 if uri not in self.plugin_info:
245 world = SimpleRDFModel()
246 world.copyFrom(self.manifests)
247 seeAlso = self.manifests.bySubject[uri]["http://www.w3.org/2000/01/rdf-schema#seeAlso"]
248 for doc in seeAlso:
249 # print "Loading " + doc + " for plugin " + uri
250 parseTTL(doc, file(doc).read(), world, self.debug)
251 self.plugin_info[uri] = world
252 info = self.plugin_info[uri]
253 dest = LV2Plugin()
254 dest.uri = uri
255 dest.name = info.bySubject[uri]['http://usefulinc.com/ns/doap#name'][0]
256 dest.license = info.bySubject[uri]['http://usefulinc.com/ns/doap#license'][0]
257 dest.classes = info.bySubject[uri]["a"]
258 dest.requiredFeatures = info.getProperty(uri, lv2 + "requiredFeature", optional = True)
259 dest.optionalFeatures = info.getProperty(uri, lv2 + "optionalFeature", optional = True)
260 dest.microname = info.getProperty(uri, tinyname_uri, optional = True)
261 if len(dest.microname):
262 dest.microname = dest.microname[0]
263 else:
264 dest.microname = None
265 ports = []
266 portDict = {}
267 porttypes = {
268 "isAudio" : lv2 + "AudioPort",
269 "isControl" : lv2 + "ControlPort",
270 "isEvent" : lv2evt + "EventPort",
271 "isInput" : lv2 + "InputPort",
272 "isOutput" : lv2 + "OutputPort",
275 for port in info.bySubject[uri][lv2 + "port"]:
276 psubj = info.bySubject[port]
277 pdata = LV2Port()
278 pdata.uri = port
279 pdata.index = int(info.getProperty(psubj, lv2 + "index")[0])
280 pdata.symbol = info.getProperty(psubj, lv2 + "symbol")[0]
281 pdata.name = info.getProperty(psubj, lv2 + "name")[0]
282 classes = set(info.getProperty(psubj, "a"))
283 pdata.classes = classes
284 for pt in porttypes.keys():
285 pdata.__dict__[pt] = porttypes[pt] in classes
286 sp = info.getProperty(psubj, lv2 + "scalePoint")
287 if sp and len(sp):
288 splist = []
289 for pt in sp:
290 name = info.getProperty(pt, rdfs + "label", optional = True, single = True)
291 if name != None:
292 value = info.getProperty(pt, rdf + "value", optional = True, single = True)
293 if value != None:
294 splist.append((name, value))
295 pdata.scalePoints = splist
296 else:
297 pdata.scalePoints = []
298 pdata.defaultValue = info.getProperty(psubj, [lv2 + "default"], optional = True, single = True)
299 pdata.minimum = info.getProperty(psubj, [lv2 + "minimum"], optional = True, single = True)
300 pdata.maximum = info.getProperty(psubj, [lv2 + "maximum"], optional = True, single = True)
301 pdata.microname = info.getProperty(psubj, [tinyname_uri], optional = True, single = True)
302 pdata.properties = set(info.getProperty(psubj, [lv2 + "portProperty"], optional = True))
303 ports.append(pdata)
304 portDict[pdata.uri] = pdata
305 ports.sort(lambda x, y: cmp(x.index, y.index))
306 dest.ports = ports
307 dest.portDict = portDict
308 return dest