annotate rdf/generator/vamp-rdf-template-generator.cpp @ 354:e85513153c71

Initialise rate to 0. Otherwise there's a danger plugins will change the SampleType (e.g. to VariableSampleRate) but not set the rate because they don't think they need it (when in fact it needs to be set to 0)
author Chris Cannam
date Thu, 28 Mar 2013 15:49:17 +0000
parents 34754c776530
children bca93a149bd7 feac60b5d82b
rev   line source
cannam@138 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
cannam@138 2
cannam@231 3 #include <vamp-hostsdk/PluginHostAdapter.h>
cannam@233 4 #include <vamp-hostsdk/PluginChannelAdapter.h>
cannam@233 5 #include <vamp-hostsdk/PluginInputDomainAdapter.h>
cannam@233 6 #include <vamp-hostsdk/PluginLoader.h>
cannam@231 7 #include <vamp/vamp.h>
cannam@138 8
cannam@138 9 #include <iostream>
cannam@138 10 #include <fstream>
cannam@138 11 #include <sstream>
cannam@138 12
cannam@138 13 #include <cmath>
cannam@232 14 #include <cstdlib>
cannam@232 15 #include <cstring>
cannam@138 16
cannam@243 17 #include <cstdlib>
cannam@243 18 #include <cstring>
cannam@243 19
cannam@138 20 using std::cout;
cannam@138 21 using std::cin;
cannam@138 22 using std::cerr;
cannam@138 23 using std::getline;
cannam@138 24 using std::endl;
cannam@138 25 using std::string;
cannam@138 26 using std::vector;
cannam@138 27 using std::ofstream;
cannam@138 28 using std::ios;
cannam@138 29
cannam@138 30 using Vamp::HostExt::PluginLoader;
cannam@138 31 using Vamp::Plugin;
cannam@138 32
cannam@217 33 //???
cannam@138 34 string programURI = "http://www.vamp-plugins.org/doap.rdf#template-generator";
cannam@138 35
cannam@138 36 void usage()
cannam@138 37 {
cannam@217 38 cerr << endl;
cannam@251 39 cerr << "vamp-rdf-template-generator: Create a skeleton RDF description file describing" << endl;
cannam@251 40 cerr << "a Vamp plugin library using the Vamp ontology." << endl;
cannam@217 41 cerr << endl;
cannam@217 42 cerr << "Usage:" << endl;
cannam@251 43 cerr << " vamp-rdf-template-generator -i vamp:soname[:plugin] [vamp:soname[:plugin] ...]" << endl;
cannam@251 44 cerr << " vamp-rdf-template-generator PLUGIN_BASE_URI [ -m YOUR_URI ] [vamp:]soname[:plugin] [[vamp:]soname[:plugin] ...]" << endl;
cannam@217 45 cerr << endl;
cannam@217 46 cerr << "Example:" << endl;
cannam@251 47 cerr << " vamp-rdf-template-generator http://vamp-plugins.org/rdf/plugins/ vamp-example-plugins" << endl;
cannam@217 48 cerr << endl;
cannam@138 49 exit(2);
cannam@138 50 }
cannam@138 51
cannam@138 52 template <class T>
cannam@138 53 inline string to_string (const T& t)
cannam@138 54 {
cannam@138 55 std::stringstream ss;
cannam@138 56 ss << t;
cannam@138 57 return ss.str();
cannam@138 58 }
cannam@138 59
cannam@144 60 string describe_namespaces(string pluginBundleBaseURI, string libname)
cannam@138 61 {
cannam@142 62 string res=\
cannam@142 63 "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n\
cannam@138 64 @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n\
cannam@147 65 @prefix vamp: <http://purl.org/ontology/vamp/> .\n\
cannam@144 66 @prefix plugbase: <"+pluginBundleBaseURI+libname+"#> .\n\
cannam@138 67 @prefix owl: <http://www.w3.org/2002/07/owl#> .\n\
cannam@138 68 @prefix dc: <http://purl.org/dc/elements/1.1/> .\n\
cannam@138 69 @prefix af: <http://purl.org/ontology/af/> .\n\
cannam@138 70 @prefix foaf: <http://xmlns.com/foaf/0.1/> .\n\
cannam@138 71 @prefix cc: <http://web.resource.org/cc/> .\n\
cannam@267 72 @prefix : <#> .\n\n";
cannam@138 73
cannam@142 74 return res;
cannam@138 75 }
cannam@138 76
cannam@144 77 string describe_doc(string describerURI, string pluginBundleBaseURI,
cannam@144 78 string libname)
cannam@138 79 {
cannam@142 80 string res=\
cannam@217 81 "<> a vamp:PluginDescription ;\n";
cannam@217 82 if (describerURI != "") {
cannam@217 83 res += " foaf:maker <"+describerURI+"> ;\n";
cannam@217 84 }
cannam@217 85 res += "\
cannam@144 86 foaf:maker <"+programURI+"> ;\n\
cannam@144 87 foaf:primaryTopic <"+pluginBundleBaseURI+libname+"> .\n\n";
cannam@142 88 return res;
cannam@138 89 }
cannam@138 90
cannam@138 91
cannam@144 92 string describe_library(string libname, vector<Plugin *> plugins)
cannam@144 93 {
cannam@144 94 string res=\
cannam@144 95 ":"+libname+" a vamp:PluginLibrary ;\n\
cannam@144 96 vamp:identifier \""+libname+"\" ";
cannam@144 97
cannam@144 98 for (size_t i = 0; i < plugins.size(); ++i) {
cannam@218 99 res += " ; \n\
cannam@145 100 vamp:available_plugin plugbase:"+plugins[i]->getIdentifier();
cannam@144 101 }
cannam@144 102
cannam@218 103 res += " ; \n\
cannam@218 104 # foaf:page <Place more-information HTML page URL here and uncomment> ;\n\
cannam@218 105 .\n\n";
cannam@144 106 return res;
cannam@144 107 }
cannam@144 108
cannam@138 109 string describe_plugin(Plugin* plugin)
cannam@138 110 {
cannam@142 111 string res=\
cannam@142 112 "plugbase:"+plugin->getIdentifier()+" a vamp:Plugin ;\n\
cannam@138 113 dc:title \""+plugin->getName()+"\" ;\n\
cannam@139 114 vamp:name \""+plugin->getName()+"\" ;\n\
cannam@221 115 dc:description \"\"\""+plugin->getDescription()+"\"\"\" ;\n\
cannam@154 116 foaf:maker [ foaf:name \""+plugin->getMaker()+"\" ] ; # FIXME could give plugin author's URI here\n\
cannam@224 117 dc:rights \"\"\""+plugin->getCopyright()+"\"\"\" ;\n\
cannam@154 118 # cc:license <Place plugin license URI here and uncomment> ; \n\
cannam@138 119 vamp:identifier \""+plugin->getIdentifier()+"\" ;\n\
cannam@138 120 vamp:vamp_API_version vamp:api_version_"+to_string(plugin->getVampApiVersion())+" ;\n\
cannam@138 121 owl:versionInfo \""+to_string(plugin->getPluginVersion())+"\" ;\n";
cannam@142 122 if (plugin->getInputDomain() == Vamp::Plugin::FrequencyDomain)
cannam@142 123 res+=" vamp:input_domain vamp:FrequencyDomain ;\n\n";
cannam@142 124 else
cannam@194 125 res+=" vamp:input_domain vamp:TimeDomain ;\n";
cannam@138 126
cannam@138 127
cannam@142 128 Plugin::ParameterList params = plugin->getParameterDescriptors();
cannam@194 129 if (!params.empty()) res+="\n";
cannam@142 130 for (Plugin::ParameterList::const_iterator i = params.begin(); i != params.end(); i++)
dpastor@156 131 res+=" vamp:parameter plugbase:"+plugin->getIdentifier()+"_param_"+(*i).identifier+" ;\n";
cannam@194 132 if (!params.empty()) res+="\n";
cannam@138 133
cannam@142 134 Plugin::OutputList outputs = plugin->getOutputDescriptors();
cannam@142 135 for (Plugin::OutputList::const_iterator i = outputs.begin(); i!= outputs.end(); i++)
cannam@158 136 res+=" vamp:output plugbase:"+plugin->getIdentifier()+"_output_"+(*i).identifier+" ;\n";
cannam@142 137 res+=" .\n";
cannam@138 138
cannam@142 139 return res;
cannam@138 140 }
cannam@138 141
cannam@144 142 string describe_param(Plugin *plugin, Plugin::ParameterDescriptor p)
cannam@138 143 {
cannam@148 144
cannam@148 145 //FIXME: dc:format and vamp:unit are the same???
dpastor@157 146 //Should be a QUantizedParameter also a Parameter??
cannam@148 147 if(p.isQuantized){
cannam@148 148 string res=\
dpastor@156 149 "plugbase:"+plugin->getIdentifier()+"_param_"+p.identifier+" a vamp:QuantizedParameter ;\n\
cannam@148 150 vamp:identifier \""+p.identifier+"\" ;\n\
cannam@148 151 dc:title \""+p.name+"\" ;\n\
cannam@148 152 dc:format \""+p.unit+"\" ;\n\
cannam@148 153 vamp:min_value "+to_string(p.minValue)+" ;\n\
cannam@148 154 vamp:max_value "+to_string(p.maxValue)+" ;\n\
cannam@148 155 vamp:unit \""+p.unit+"\" ;\n\
cannam@152 156 vamp:quantize_step "+to_string(p.quantizeStep)+" ;\n\
cannam@148 157 vamp:default_value "+to_string(p.defaultValue)+" ;\n\
cannam@148 158 vamp:value_names (";
cannam@148 159
cannam@148 160 unsigned int i;
cannam@148 161 for (i=0; i+1 < p.valueNames.size(); i++)
cannam@148 162 res+=" \""+p.valueNames[i]+"\"";
cannam@148 163 if (i < p.valueNames.size())
cannam@148 164 res+=" \""+p.valueNames[i]+"\"";
cannam@148 165 res+=");\n";
cannam@148 166
cannam@148 167 res+=" .\n";
cannam@148 168
cannam@148 169 return res;
cannam@148 170
cannam@148 171 }else{
cannam@142 172 string res=\
dpastor@156 173 "plugbase:"+plugin->getIdentifier()+"_param_"+p.identifier+" a vamp:Parameter ;\n\
cannam@138 174 vamp:identifier \""+p.identifier+"\" ;\n\
cannam@138 175 dc:title \""+p.name+"\" ;\n\
cannam@138 176 dc:format \""+p.unit+"\" ;\n\
cannam@139 177 vamp:min_value "+to_string(p.minValue)+" ;\n\
cannam@139 178 vamp:max_value "+to_string(p.maxValue)+" ;\n\
cannam@148 179 vamp:unit \""+p.unit+"\" ;\n\
cannam@148 180 vamp:default_value "+to_string(p.defaultValue)+" ;\n\
cannam@148 181 vamp:value_names (";
cannam@148 182
cannam@148 183 unsigned int i;
cannam@148 184 for (i=0; i+1 < p.valueNames.size(); i++)
cannam@148 185 res+=" \""+p.valueNames[i]+"\"";
cannam@148 186 if (i < p.valueNames.size())
cannam@148 187 res+=" \""+p.valueNames[i]+"\"";
cannam@148 188 res+=");\n";
cannam@148 189
cannam@148 190 res+=" .\n";
cannam@148 191
cannam@142 192 return res;
cannam@148 193
cannam@148 194 }
cannam@138 195 }
cannam@138 196
cannam@144 197 string describe_output(Plugin *plugin, Plugin::OutputDescriptor o)
cannam@138 198 {
cannam@138 199
cannam@142 200 //we need to distinguish here between different output types:
cannam@148 201
cannam@148 202 //Quantize or not
cannam@148 203 //KnownExtents or not
cannam@148 204 //Data output classification:
cannam@142 205 //DenseOutput
cannam@142 206 //SparseOutput
cannam@142 207 //TrackLevelOutput
cannam@139 208
cannam@139 209
cannam@168 210 // SparseOutput: variable sample rate. Events are not evenly
cannam@168 211 // spaced so we need to record the time associated with the event
cannam@168 212 // as it its not ensured that we have an event after the next one
cannam@168 213 // (but there is not time to set the duration, it has to be
cannam@168 214 // calculated as the different between 2 different events). The
cannam@168 215 // timestamp must be read.
cannam@139 216
cannam@142 217 string res;
cannam@139 218
cannam@168 219 if (o.sampleType == Plugin::OutputDescriptor::VariableSampleRate ||
cannam@168 220 !o.hasFixedBinCount)
cannam@142 221 {
cannam@139 222
cannam@142 223 res=\
cannam@144 224 "plugbase:"+plugin->getIdentifier()+"_output_"+o.identifier+" a vamp:SparseOutput ;\n\
cannam@139 225 vamp:identifier \""+o.identifier+"\" ;\n\
cannam@139 226 dc:title \""+o.name+"\" ;\n\
cannam@273 227 dc:description \"\"\""+o.description+"\"\"\" ;\n\
cannam@139 228 vamp:fixed_bin_count \""+(o.hasFixedBinCount == 1 ? "true" : "false")+"\" ;\n\
cannam@139 229 vamp:unit \""+(o.unit)+"\" ;\n";
cannam@139 230
cannam@148 231
cannam@148 232 //another type of output
cannam@148 233 if(o.isQuantized){
cannam@148 234
cannam@148 235 res+=" a vamp:QuantizedOutput ;\n";
cannam@148 236 res+=" vamp:quantize_step "+to_string(o.quantizeStep)+" ;\n";
cannam@148 237 }
cannam@148 238
cannam@148 239 //and yet another type
cannam@148 240 if(o.hasKnownExtents){
cannam@148 241
cannam@148 242 res+=" a vamp:KnownExtentsOutput ;\n";
cannam@148 243 res+=" vamp:min_value "+to_string(o.minValue)+" ;\n";
cannam@148 244 res+=" vamp:max_value "+to_string(o.maxValue)+" ;\n";
cannam@148 245 }
cannam@139 246
cannam@142 247 // FIXME ? Bin names may vary based on plugin setup, so including them here might be misleading...
cannam@142 248 if (o.hasFixedBinCount)
cannam@142 249 {
cannam@144 250 res+=" vamp:bin_count "+to_string(o.binCount)+" ;\n";
cannam@138 251
cannam@264 252 bool haveBinNames = false;
cannam@308 253 for (unsigned int i=0; i < o.binNames.size(); i++) {
cannam@264 254 if (o.binNames[i] != "") {
cannam@264 255 haveBinNames = true;
cannam@264 256 break;
cannam@264 257 }
cannam@264 258 }
cannam@264 259
cannam@264 260 if (haveBinNames) {
cannam@264 261 res+=" vamp:bin_names (";
cannam@264 262
cannam@264 263 unsigned int i;
cannam@264 264 for (i=0; i+1 < o.binNames.size(); i++)
cannam@264 265 res+=" \""+o.binNames[i]+"\"";
cannam@264 266 if (i < o.binNames.size())
cannam@264 267 res+=" \""+o.binNames[i]+"\"";
cannam@264 268 res+=");\n";
cannam@264 269 }
cannam@142 270 }
cannam@148 271
cannam@144 272 res+=" vamp:sample_type vamp:VariableSampleRate ;\n";
cannam@142 273 if (o.sampleRate > 0.0f)
cannam@144 274 res+=" vamp:sample_rate "+to_string(o.sampleRate)+" ;\n";
cannam@142 275
cannam@142 276 }
cannam@139 277
cannam@142 278 //If we do not have SparseOutput, then we have DenseOutput. TrackLevelOutput can not be inferred from the plugin directly without actually
cannam@142 279 //running the plugin.
cannam@142 280 else{
cannam@142 281
cannam@142 282 res=\
cannam@144 283 "plugbase:"+plugin->getIdentifier()+"_output_"+o.identifier+" a vamp:DenseOutput ;\n\
cannam@139 284 vamp:identifier \""+o.identifier+"\" ;\n\
cannam@139 285 dc:title \""+o.name+"\" ;\n\
cannam@273 286 dc:description \"\"\""+o.description+"\"\"\" ;\n\
cannam@139 287 vamp:fixed_bin_count \""+(o.hasFixedBinCount == 1 ? "true" : "false")+"\" ;\n\
cannam@139 288 vamp:unit \""+(o.unit)+"\" ;\n";
cannam@139 289
cannam@139 290
cannam@148 291 //another type of output
cannam@148 292 if(o.isQuantized){
cannam@148 293
cannam@148 294 res+=" a vamp:QuantizedOutput ;\n";
cannam@148 295 res+=" vamp:quantize_step "+to_string(o.quantizeStep)+" ;\n";
cannam@148 296 }
cannam@148 297
cannam@148 298 //and yet another type
cannam@148 299 if(o.hasKnownExtents){
cannam@148 300
cannam@148 301 res+=" a vamp:KnownExtentsOutput ;\n";
cannam@148 302 res+=" vamp:min_value "+to_string(o.minValue)+" ;\n";
cannam@148 303 res+=" vamp:max_value "+to_string(o.maxValue)+" ;\n";
cannam@148 304 }
cannam@139 305
cannam@142 306 // FIXME ? Bin names may vary based on plugin setup, so including them here might be misleading...
cannam@142 307 if (o.hasFixedBinCount)
cannam@142 308 {
cannam@144 309 res+=" vamp:bin_count "+to_string(o.binCount)+" ;\n";
cannam@139 310
cannam@264 311 bool haveBinNames = false;
cannam@308 312 for (unsigned int i=0; i < o.binNames.size(); i++) {
cannam@264 313 if (o.binNames[i] != "") {
cannam@264 314 haveBinNames = true;
cannam@264 315 break;
cannam@264 316 }
cannam@264 317 }
cannam@264 318
cannam@264 319 if (haveBinNames) {
cannam@264 320 res+=" vamp:bin_names (";
cannam@264 321
cannam@264 322 unsigned int i;
cannam@264 323 for (i=0; i+1 < o.binNames.size(); i++)
cannam@264 324 res+=" \""+o.binNames[i]+"\"";
cannam@264 325 if (i < o.binNames.size())
cannam@264 326 res+=" \""+o.binNames[i]+"\"";
cannam@264 327 res+=");\n";
cannam@264 328 }
cannam@139 329 }
cannam@139 330
cannam@142 331 else if (o.sampleType == Plugin::OutputDescriptor::FixedSampleRate)
cannam@142 332 {
cannam@144 333 res+=" vamp:sample_type vamp:FixedSampleRate ;\n";
cannam@144 334 res+=" vamp:sample_rate "+to_string(o.sampleRate)+" ;\n";
cannam@142 335 }
cannam@142 336 else if (o.sampleType == Plugin::OutputDescriptor::OneSamplePerStep)
cannam@144 337 res+=" vamp:sample_type vamp:OneSamplePerStep ;\n";
cannam@142 338 else
cannam@142 339 {
cannam@142 340 cerr<<"Incomprehensible sampleType for output descriptor "+o.identifier<<" !"<<endl;
cannam@142 341 exit(1);
cannam@142 342 }
cannam@142 343 }
cannam@142 344
cannam@142 345 //There is no way to know this in advance, but we can use the km a bit for this.
cannam@159 346 res+="# vamp:computes_event_type <Place event type URI here and uncomment> ;\n";
cannam@159 347 res+="# vamp:computes_feature <Place feature attribute URI here and uncomment> ;\n";
cannam@159 348 res+="# vamp:computes_signal_type <Place signal type URI here and uncomment> ;\n";
cannam@142 349 res+=" .\n";
cannam@142 350
cannam@142 351 return res;
cannam@138 352 }
cannam@139 353
cannam@144 354 string describe(vector<Plugin *> plugins, string pluginBundleBaseURI,
cannam@144 355 string describerURI, string libname)
cannam@138 356 {
cannam@144 357 string res = describe_namespaces(pluginBundleBaseURI, libname);
cannam@138 358
cannam@144 359 res += describe_doc(describerURI, pluginBundleBaseURI, libname);
cannam@138 360
cannam@144 361 res += describe_library(libname, plugins);
cannam@144 362
cannam@144 363 for (size_t i = 0; i < plugins.size(); ++i) {
cannam@144 364
cannam@144 365 Plugin *plugin = plugins[i];
cannam@144 366
cannam@144 367 res += describe_plugin(plugin);
cannam@138 368
cannam@144 369 Plugin::ParameterList params = plugin->getParameterDescriptors();
cannam@144 370 for (Plugin::ParameterList::const_iterator i = params.begin(); i != params.end(); i++)
cannam@144 371 res += describe_param(plugin, *i);
cannam@138 372
cannam@144 373 Plugin::OutputList outputs = plugin->getOutputDescriptors();
cannam@144 374 for (Plugin::OutputList::const_iterator i = outputs.begin(); i!= outputs.end(); i++)
cannam@144 375 res += describe_output(plugin, *i);
cannam@144 376 }
cannam@138 377
cannam@142 378 return res;
cannam@138 379 }
cannam@138 380
cannam@138 381 int main(int argc, char **argv)
cannam@138 382 {
cannam@144 383 if (argc < 3) usage();
cannam@138 384
cannam@144 385 bool interactive = false;
cannam@144 386 if (!strcmp(argv[1], "-i")) interactive = true;
cannam@138 387
cannam@217 388 if (!interactive && argc < 3) usage();
cannam@138 389
cannam@138 390 string pluginBundleBaseURI, describerURI;
cannam@144 391
cannam@144 392 int argidx = 2;
cannam@138 393
cannam@144 394 if (!interactive) {
cannam@138 395 pluginBundleBaseURI = argv[1];
cannam@217 396 if (!strcmp(argv[2], "-m")) {
cannam@217 397 if (argc < 5) usage();
cannam@217 398 describerURI = argv[3];
cannam@217 399 argidx = 4;
cannam@217 400 }
cannam@144 401 } else {
cannam@138 402 cerr << "Please enter the base URI for the plugin bundle : ";
cannam@138 403 getline(cin, pluginBundleBaseURI);
cannam@217 404 cerr << "Please enter your URI (empty to omit) : ";
cannam@138 405 getline(cin, describerURI);
cannam@138 406 }
cannam@144 407
cannam@144 408 vector<Plugin *> plugins;
cannam@144 409 string libname;
cannam@144 410
cannam@144 411 PluginLoader *loader = PluginLoader::getInstance();
cannam@144 412
cannam@144 413 while (argidx < argc) {
cannam@144 414
cannam@144 415 string pluginName = argv[argidx];
cannam@144 416
cannam@144 417 if (pluginName.substr(0, 5) == "vamp:") {
cannam@144 418 pluginName = pluginName.substr(5);
cannam@144 419 }
cannam@144 420
cannam@144 421 string mylibname = pluginName.substr(0, pluginName.find(':'));
cannam@144 422
cannam@144 423 if (libname == "") libname = mylibname;
cannam@144 424 else if (libname != mylibname) {
cannam@144 425 cerr << "ERROR: All plugins specified on command line must originate in the same library" << endl;
cannam@144 426 exit(1);
cannam@144 427 }
cannam@144 428
cannam@144 429 if (mylibname == pluginName) { // pluginName is a library, not a plugin
cannam@144 430
cannam@144 431 PluginLoader::PluginKeyList list = loader->listPlugins();
cannam@144 432 for (size_t i = 0; i < list.size(); ++i) {
cannam@144 433 string thislibname = list[i].substr(0, list[i].find(':'));
cannam@144 434 if (thislibname != mylibname) continue;
cannam@144 435 Plugin *plugin = loader->loadPlugin(list[i], 44100);
cannam@144 436 if (!plugin) {
cannam@144 437 cerr << "ERROR: Plugin \"" << list[i] << "\" could not be loaded" << endl;
cannam@144 438 exit(1);
cannam@144 439 }
cannam@144 440 plugins.push_back(plugin);
cannam@144 441 }
cannam@151 442
cannam@151 443 if (plugins.empty()) {
cannam@151 444 cerr << "ERROR: Plugin library \"" << mylibname << "\" does not exist, could not be opened, or contains no plugins" << endl;
cannam@151 445 exit(1);
cannam@151 446 }
cannam@151 447
cannam@144 448 } else { // pluginName is a plugin
cannam@144 449
cannam@144 450 Plugin *plugin = loader->loadPlugin(pluginName, size_t(44100));
cannam@144 451 if (!plugin) {
cannam@144 452 cerr << "ERROR: Plugin \"" << pluginName << "\" could not be loaded" << endl;
cannam@144 453 exit(1);
cannam@144 454 }
cannam@144 455 plugins.push_back(plugin);
cannam@144 456 }
cannam@144 457
cannam@144 458 ++argidx;
cannam@144 459 }
cannam@138 460
cannam@144 461 cout << describe(plugins, pluginBundleBaseURI, describerURI, libname) << endl;
cannam@138 462
cannam@138 463 return 0;
cannam@138 464 }
cannam@138 465
cannam@138 466