annotate runner/main.cpp @ 121:5200446bbc6b test-reorg

Check for multiple transforms. But one of these tests fails, so we'd better go back to development branch and sort out why!
author Chris Cannam
date Wed, 08 Oct 2014 15:38:34 +0100
parents dcecd6997214
children 685e5cf3cb1d
rev   line source
Chris@0 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@0 2
Chris@0 3 /*
Chris@0 4 Sonic Annotator
Chris@0 5 A utility for batch feature extraction from audio files.
Chris@0 6 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
Chris@0 7 Copyright 2007-2008 QMUL.
Chris@0 8
Chris@0 9 This program is free software; you can redistribute it and/or
Chris@0 10 modify it under the terms of the GNU General Public License as
Chris@0 11 published by the Free Software Foundation; either version 2 of the
Chris@0 12 License, or (at your option) any later version. See the file
Chris@0 13 COPYING included with this distribution for more information.
Chris@0 14 */
Chris@0 15
Chris@0 16 #include <vector>
Chris@0 17 #include <string>
Chris@0 18 #include <iostream>
Chris@0 19
Chris@0 20 #include <QCoreApplication>
Chris@0 21 #include <QSettings>
Chris@0 22 #include <QStringList>
Chris@0 23 #include <QString>
Chris@0 24 #include <QFileInfo>
Chris@0 25 #include <QDir>
Chris@45 26 #include <QSet>
Chris@0 27
Chris@0 28 using std::cout;
Chris@0 29 using std::cerr;
Chris@0 30 using std::endl;
Chris@0 31 using std::vector;
Chris@0 32 using std::string;
Chris@0 33
Chris@2 34 #include "../version.h"
Chris@2 35
Chris@0 36 #include "base/Exceptions.h"
Chris@0 37 #include "base/TempDirectory.h"
Chris@111 38 #include "base/ProgressPrinter.h"
Chris@0 39
Chris@0 40 #include "data/fileio/AudioFileReaderFactory.h"
Chris@0 41 #include "data/fileio/PlaylistFileReader.h"
Chris@0 42
Chris@0 43 #include "transform/Transform.h"
Chris@0 44 #include "transform/TransformFactory.h"
Chris@0 45
Chris@0 46 #include "FeatureExtractionManager.h"
Chris@0 47 #include "transform/FeatureWriter.h"
Chris@0 48 #include "FeatureWriterFactory.h"
Chris@0 49
Chris@0 50 #include "rdf/RDFTransformFactory.h"
Chris@0 51
Chris@0 52 #include <vamp-hostsdk/PluginSummarisingAdapter.h>
Chris@0 53
Chris@0 54 #ifdef HAVE_FFTW3
Chris@0 55 #include <fftw3.h>
Chris@0 56 #endif
Chris@0 57
Chris@0 58 // Desired options:
Chris@0 59 //
Chris@0 60 // * output preference:
Chris@0 61 // - all data in one file
Chris@0 62 // - one file per input file
Chris@0 63 // - one file per input file per transform
Chris@0 64 // - (any use for: one file per transform?)
Chris@0 65 //
Chris@0 66 // * output location:
Chris@0 67 // - same directory as input file
Chris@0 68 // - current directory
Chris@0 69 //
Chris@0 70 // * output filename:
Chris@0 71 // - based on input (obvious choice for one file per input file modes)
Chris@0 72 // - specified on command line (obvious choice for all in one file mode)
Chris@0 73 //
Chris@0 74 // * output format: one or more of
Chris@0 75 // - RDF
Chris@0 76 // - AudioDB
Chris@0 77 // - Vamp Simple Host format
Chris@0 78 // - CSV
Chris@0 79 //
Chris@0 80 // * input handling:
Chris@0 81 // - run each transform on each input file separately
Chris@0 82 // - provide all input files to the same transform, one per channel
Chris@0 83 //
Chris@0 84 // * format-specific options:
Chris@0 85 // - RDF format: fancy/plain RDF
Chris@0 86 // - CSV format: separator, timestamp type
Chris@0 87 // note: do the output file/location also count as format-specific options?
Chris@0 88 // an output writer that wrote to a database would have different options...
Chris@0 89 //
Chris@0 90 // * debug level and progress output
Chris@0 91 //
Chris@0 92 // * other potential options:
Chris@0 93 // - ignore version mismatches in Transform specifications
Chris@0 94 // - sample rate: force a given rate; use file rate instead of rate in
Chris@0 95 // Transform spec
Chris@0 96 //
Chris@0 97 // * other potential instructions:
Chris@0 98 // - write out a skeleton Transform file for a specified plugin
Chris@0 99 // - write out skeleton RDF for a plugin library (i.e. do the job of
Chris@0 100 // RDF template_generator)
Chris@0 101 // - verify that RDF for a plugin library matches the plugin
Chris@0 102 //
Chris@0 103 // MAYBE:
Chris@0 104 // * transform(s) to run:
Chris@0 105 // - supply transform file names on command line
Chris@0 106 // - use all transforms found in a given directory?
Chris@0 107 //
Chris@0 108 // MAYBE:
Chris@0 109 // * input files to transform:
Chris@0 110 // - supply file names or URIs on command line
Chris@0 111 // - use all files in a given directory or tree
Chris@0 112
Chris@0 113 static QString
Chris@0 114 wrap(QString s, int len, int pfx = 0)
Chris@0 115 {
Chris@0 116 QString ws;
Chris@0 117 QStringList sl(s.split(' '));
Chris@0 118 int i = 0, c = 0;
Chris@0 119 while (i < sl.size()) {
Chris@0 120 int wl = sl[i].length();
Chris@0 121 if (c + wl < len) {
Chris@0 122 if (c > 0) {
Chris@0 123 ws += ' ';
Chris@0 124 ++c;
Chris@0 125 }
Chris@0 126 } else {
Chris@0 127 if (c > 0) {
Chris@0 128 ws += '\n';
Chris@0 129 for (int j = 0; j < pfx; ++j) ws += ' ';
Chris@0 130 c = 0;
Chris@0 131 }
Chris@0 132 }
Chris@0 133 ws += sl[i];
Chris@0 134 c += wl;
Chris@0 135 ++i;
Chris@0 136 }
Chris@0 137 return ws;
Chris@0 138 }
Chris@0 139
Chris@0 140 void usage(QString myname)
Chris@0 141 {
Chris@0 142 set<string> writers = FeatureWriterFactory::getWriterTags();
Chris@0 143
Chris@0 144 cerr << endl;
Chris@2 145 cerr << "Sonic Annotator v" << RUNNER_VERSION << endl;
Chris@0 146 cerr << "A utility for batch feature extraction from audio files." << endl;
Chris@0 147 cerr << "Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London." << endl;
Chris@95 148 cerr << "Copyright 2007-2014 Queen Mary, University of London." << endl;
Chris@0 149 cerr << endl;
Chris@0 150 cerr << "This program is free software. You may redistribute copies of it under the" << endl;
Chris@0 151 cerr << "terms of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>." << endl;
Chris@0 152 cerr << "This program is supplied with NO WARRANTY, to the extent permitted by law." << endl;
Chris@0 153 cerr << endl;
Chris@0 154 cerr << " Usage: " << myname.toStdString()
Chris@0 155 << " [-mr] -t trans.xml [...] -w <writer> [...] <audio> [...]" << endl;
Chris@0 156 cerr << " " << myname.toStdString()
Chris@0 157 << " [-mr] -T trans.txt [...] -w <writer> [...] <audio> [...]" << endl;
Chris@0 158 cerr << " " << myname.toStdString()
Chris@0 159 << " -s <transform>" << endl;
Chris@0 160 cerr << " " << myname.toStdString()
Chris@46 161 << " [-lhv]" << endl;
Chris@0 162 cerr << endl;
Chris@0 163 cerr << "Where <audio> is an audio file or URL to use as input: either a local file" << endl;
Chris@0 164 cerr << "path, local \"file://\" URL, or remote \"http://\" or \"ftp://\" URL." << endl;
Chris@0 165 cerr << endl;
Chris@0 166
Chris@0 167 QString extensions = AudioFileReaderFactory::getKnownExtensions();
Chris@0 168 QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
Chris@0 169 if (!extlist.empty()) {
Chris@0 170 cerr << "The following audio file extensions are recognised:" << endl;
Chris@0 171 cerr << " ";
Chris@0 172 int c = 2;
Chris@0 173 for (int i = 0; i < extlist.size(); ++i) {
Chris@0 174 QString ext = extlist[i];
Chris@0 175 if (ext.startsWith("*.")) ext = ext.right(ext.length()-2);
Chris@0 176 c += ext.length() + 2;
Chris@0 177 if (c >= 80) {
Chris@0 178 cerr << "\n ";
Chris@0 179 c -= 78;
Chris@0 180 }
Chris@0 181 cerr << ext.toStdString();
Chris@0 182 if (i + 1 == extlist.size()) cerr << ".";
Chris@0 183 else cerr << ", ";
Chris@0 184 }
Chris@0 185 cerr << endl;
Chris@0 186 }
Chris@0 187
Chris@0 188 cerr << "Playlist files in M3U format are also supported." << endl;
Chris@0 189 cerr << endl;
Chris@0 190 cerr << "Transformation options:" << endl;
Chris@0 191 cerr << endl;
Chris@0 192 cerr << " -t, --transform <T> Apply transform described in transform file <T> to" << endl;
Chris@0 193 cerr << " all input audio files. You may supply this option" << endl;
Chris@0 194 cerr << " multiple times. You must supply this option or -T at" << endl;
Chris@0 195 cerr << " least once for any work to be done. Transform format" << endl;
Chris@0 196 cerr << " may be SV transform XML or Vamp transform RDF. See" << endl;
Chris@0 197 cerr << " documentation for examples." << endl;
Chris@0 198 cerr << endl;
Chris@0 199 cerr << " -T, --transforms <T> Apply all transforms described in transform files" << endl;
Chris@0 200 cerr << " whose names are listed in text file <T>. You may supply" << endl;
Chris@0 201 cerr << " this option multiple times." << endl;
Chris@0 202 cerr << endl;
Chris@0 203 cerr << " -d, --default <I> Apply the default transform for transform id <I>. This" << endl;
Chris@0 204 cerr << " is equivalent to generating a skeleton transform for this" << endl;
Chris@0 205 cerr << " id (using the -s option, below) and then applying that," << endl;
Chris@0 206 cerr << " unmodified, with the -t option in the normal way. Note" << endl;
Chris@0 207 cerr << " that the results may vary as the implementation's default" << endl;
Chris@0 208 cerr << " processing parameters are not guaranteed. Do not use" << endl;
Chris@0 209 cerr << " this in production systems. You may supply this option" << endl;
Chris@0 210 cerr << " multiple times, and mix it with -t and -T." << endl;
Chris@0 211 cerr << endl;
Chris@0 212 cerr << " -w, --writer <W> Write output using writer type <W>." << endl;
Chris@0 213 cerr << " Supported writer types are: ";
Chris@0 214 for (set<string>::const_iterator i = writers.begin();
Chris@0 215 i != writers.end(); ) {
Chris@0 216 cerr << *i;
Chris@0 217 if (++i != writers.end()) cerr << ", ";
Chris@0 218 else cerr << ".";
Chris@0 219 }
Chris@0 220 cerr << endl;
Chris@0 221 cerr << " You may supply this option multiple times. You must" << endl;
Chris@0 222 cerr << " supply this option at least once for any work to be done." << endl;
Chris@0 223 cerr << endl;
Chris@0 224 cerr << " -S, --summary <S> In addition to the result features, write summary feature" << endl;
Chris@0 225 cerr << " of summary type <S>." << endl;
Chris@0 226 cerr << " Supported summary types are: min, max, mean, median, mode," << endl;
Chris@0 227 cerr << " sum, variance, sd, count." << endl;
Chris@0 228 cerr << " You may supply this option multiple times." << endl;
Chris@0 229 cerr << endl;
Chris@0 230 cerr << " --summary-only Write only summary features; do not write the regular" << endl;
Chris@0 231 cerr << " result features." << endl;
Chris@0 232 cerr << endl;
Chris@0 233 cerr << " --segments <A>,<B>[,...]" << endl;
Chris@0 234 cerr << " Summarise in segments, with segment boundaries" << endl;
Chris@0 235 cerr << " at A, B, ... seconds." << endl;
Chris@0 236 cerr << endl;
Chris@0 237 cerr << " -m, --multiplex If multiple input audio files are given, use mono" << endl;
Chris@0 238 cerr << " mixdowns of all files as the input channels for a single" << endl;
Chris@0 239 cerr << " invocation of each transform, instead of running the" << endl;
Chris@106 240 cerr << " transform against all files separately. The first file" << endl;
Chris@106 241 cerr << " will be used for output reference name and sample rate." << endl;
Chris@0 242 cerr << endl;
Chris@0 243 cerr << " -r, --recursive If any of the <audio> arguments is found to be a local" << endl;
Chris@0 244 cerr << " directory, search the tree starting at that directory" << endl;
Chris@0 245 cerr << " for all supported audio files and take all of those as" << endl;
Chris@0 246 cerr << " input instead." << endl;
Chris@0 247 cerr << endl;
Chris@116 248 cerr << " -n, --normalise Normalise input audio files to signal absolute max = 1.f." << endl;
Chris@116 249 cerr << endl;
Chris@21 250 cerr << " -f, --force Continue with subsequent files following an error." << endl;
Chris@21 251 cerr << endl;
Chris@0 252 cerr << "Housekeeping options:" << endl;
Chris@0 253 cerr << endl;
Chris@0 254 cerr << " -l, --list List all known transform ids to standard output." << endl;
Chris@0 255 cerr << endl;
Chris@0 256 cerr << " -s, --skeleton <I> Generate a skeleton transform file for transform id <I>" << endl;
Chris@0 257 cerr << " and write it to standard output." << endl;
Chris@0 258 cerr << endl;
Chris@46 259 cerr << " -v, --version Show the version number and exit." << endl;
Chris@0 260 cerr << " -h, --help Show this help." << endl;
Chris@0 261
Chris@0 262 cerr << endl;
Chris@46 263 cerr << "If no -w (or --writer) options are supplied, either the -l -s -v or -h option" << endl;
Chris@46 264 cerr << "(or long equivalent) must be given instead." << endl;
Chris@0 265
Chris@0 266 for (set<string>::const_iterator i = writers.begin();
Chris@0 267 i != writers.end(); ++i) {
Chris@0 268 FeatureWriter *w = FeatureWriterFactory::createWriter(*i);
Chris@0 269 if (!w) {
Chris@0 270 cerr << " (Internal error: failed to create writer of this type)" << endl;
Chris@0 271 continue;
Chris@0 272 }
Chris@0 273 FeatureWriter::ParameterList params = w->getSupportedParameters();
Chris@0 274 delete w;
Chris@0 275 if (params.empty()) {
Chris@0 276 continue;
Chris@0 277 }
Chris@0 278 cerr << endl;
Chris@0 279 cerr << "Additional options for writer type \"" << *i << "\":" << endl;
Chris@0 280 cerr << endl;
Chris@0 281 for (FeatureWriter::ParameterList::const_iterator j = params.begin();
Chris@0 282 j != params.end(); ++j) {
Chris@0 283 cerr << " --" << *i << "-" << j->name << " ";
Chris@0 284 int spaceage = 16 - int(i->length()) - int(j->name.length());
Chris@0 285 if (j->hasArg) { cerr << "<X> "; spaceage -= 4; }
Chris@0 286 for (int k = 0; k < spaceage; ++k) cerr << " ";
Chris@0 287 QString s(j->description.c_str());
Chris@0 288 s = wrap(s, 56, 22);
Chris@0 289 cerr << s.toStdString() << endl;
Chris@0 290 }
Chris@0 291 }
Chris@0 292
Chris@0 293 cerr << endl;
Chris@0 294 exit(0);
Chris@0 295 }
Chris@0 296
Chris@0 297 void
Chris@0 298 listTransforms()
Chris@0 299 {
Chris@0 300 TransformList transforms =
Chris@0 301 TransformFactory::getInstance()->getAllTransformDescriptions();
Chris@0 302
Chris@0 303 for (TransformList::const_iterator iter = transforms.begin();
Chris@0 304 iter != transforms.end(); ++iter) {
Chris@0 305 const TransformDescription &transform = *iter;
Chris@0 306 if (transform.type == TransformDescription::Analysis) {
Chris@0 307 cout << transform.identifier.toStdString() << endl;
Chris@0 308 }
Chris@0 309 }
Chris@0 310 }
Chris@0 311
Chris@0 312 void
Chris@0 313 printSkeleton(QString id)
Chris@0 314 {
Chris@0 315 Transform transform =
Chris@0 316 TransformFactory::getInstance()->getDefaultTransformFor(id);
Chris@0 317 cout << "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> ." << endl
Chris@0 318 << "@prefix vamp: <http://purl.org/ontology/vamp/> ." << endl
Chris@0 319 << "@prefix : <#> ." << endl << endl;
Chris@0 320 QString rdf = RDFTransformFactory::writeTransformToRDF
Chris@0 321 (transform, ":transform");
Chris@0 322 cout << rdf.toStdString();
Chris@0 323 }
Chris@0 324
Chris@0 325 void
Chris@0 326 findSourcesRecursive(QString dirname, QStringList &addTo, int &found)
Chris@0 327 {
Chris@0 328 QDir dir(dirname);
Chris@0 329
Chris@0 330 QString printable = dir.dirName().left(20);
Chris@0 331 cerr << "\rScanning \"" << printable.toStdString() << "\"..."
Chris@0 332 << QString(" ").left(20 - printable.length()).toStdString()
Chris@0 333 << " [" << found << " audio file(s)]";
Chris@0 334
Chris@0 335 QString extensions = AudioFileReaderFactory::getKnownExtensions();
Chris@0 336 QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
Chris@0 337
Chris@0 338 QStringList files = dir.entryList
Chris@0 339 (extlist, QDir::Files | QDir::Readable);
Chris@0 340 for (int i = 0; i < files.size(); ++i) {
Chris@0 341 addTo.push_back(dir.filePath(files[i]));
Chris@0 342 ++found;
Chris@0 343 }
Chris@0 344
Chris@0 345 QStringList subdirs = dir.entryList
Chris@0 346 (QStringList(), QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
Chris@0 347 for (int i = 0; i < subdirs.size(); ++i) {
Chris@0 348 findSourcesRecursive(dir.filePath(subdirs[i]), addTo, found);
Chris@0 349 }
Chris@0 350 }
Chris@0 351
Chris@111 352 QStringList
Chris@111 353 expandPlaylists(QStringList sources)
Chris@111 354 {
Chris@111 355 QStringList expanded;
Chris@111 356 foreach (QString path, sources) {
Chris@111 357 if (QFileInfo(path).suffix().toLower() == "m3u") {
Chris@111 358 ProgressPrinter retrievalProgress("Opening playlist file...");
Chris@111 359 FileSource source(path, &retrievalProgress);
Chris@111 360 if (!source.isAvailable()) {
Chris@117 361 // Don't fail or throw an exception here, just keep
Chris@117 362 // the file in the list -- it will be tested again
Chris@117 363 // when adding it as a source and that's the proper
Chris@117 364 // time to fail. All we're concluding here is that it
Chris@117 365 // isn't a valid playlist
Chris@117 366 expanded.push_back(path);
Chris@117 367 continue;
Chris@111 368 }
Chris@111 369 source.waitForData();
Chris@111 370 PlaylistFileReader reader(source);
Chris@111 371 if (reader.isOK()) {
Chris@111 372 vector<QString> files = reader.load();
Chris@111 373 for (int i = 0; i < (int)files.size(); ++i) {
Chris@111 374 expanded.push_back(files[i]);
Chris@111 375 }
Chris@111 376 }
Chris@111 377 } else {
Chris@111 378 // not a playlist
Chris@111 379 expanded.push_back(path);
Chris@111 380 }
Chris@111 381 }
Chris@111 382 return expanded;
Chris@111 383 }
Chris@0 384
Chris@0 385 int main(int argc, char **argv)
Chris@0 386 {
Chris@0 387 QCoreApplication application(argc, argv);
Chris@0 388
Chris@0 389 QCoreApplication::setOrganizationName("QMUL");
Chris@0 390 QCoreApplication::setOrganizationDomain("qmul.ac.uk");
Chris@0 391 QCoreApplication::setApplicationName("Sonic Annotator");
Chris@0 392
Chris@0 393 QStringList args = application.arguments();
Chris@0 394 set<string> requestedWriterTags;
Chris@0 395 set<string> requestedTransformFiles;
Chris@0 396 set<string> requestedTransformListFiles;
Chris@0 397 set<string> requestedDefaultTransforms;
Chris@0 398 set<string> requestedSummaryTypes;
Chris@21 399 bool force = false;
Chris@106 400 bool multiplex = false;
Chris@0 401 bool recursive = false;
Chris@116 402 bool normalise = false;
Chris@0 403 bool list = false;
Chris@0 404 bool summaryOnly = false;
Chris@0 405 QString skeletonFor = "";
Chris@0 406 QString myname = args[0];
Chris@0 407 myname = QFileInfo(myname).baseName();
Chris@0 408 QStringList otherArgs;
Chris@0 409 Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries boundaries;
Chris@0 410
Chris@0 411 QString helpStr = myname + ": use -h or --help option for help";
Chris@0 412
Chris@0 413 for (int i = 1; i < args.size(); ++i) {
Chris@0 414
Chris@0 415 QString arg = args[i];
Chris@0 416 bool last = ((i + 1) == args.size());
Chris@0 417
Chris@0 418 if (arg == "-h" || arg == "--help" || arg == "-?") {
Chris@0 419 usage(myname);
Chris@0 420 }
Chris@0 421
Chris@46 422 if (arg == "-v" || arg == "--version") {
Chris@46 423 std::cout << RUNNER_VERSION << std::endl;
Chris@46 424 return 0;
Chris@46 425 }
Chris@46 426
Chris@0 427 if (arg == "-w" || arg == "--writer") {
Chris@0 428 if (last || args[i+1].startsWith("-")) {
Chris@0 429 cerr << myname.toStdString() << ": argument expected for \""
Chris@0 430 << arg.toStdString() << "\" option" << endl;
Chris@0 431 cerr << helpStr.toStdString() << endl;
Chris@0 432 exit(2);
Chris@0 433 } else {
Chris@0 434 string tag = args[++i].toStdString();
Chris@0 435 if (requestedWriterTags.find(tag) != requestedWriterTags.end()) {
Chris@0 436 cerr << myname.toStdString() << ": NOTE: duplicate specification of writer type \"" << tag << "\" ignored" << endl;
Chris@0 437 } else {
Chris@0 438 requestedWriterTags.insert(tag);
Chris@0 439 }
Chris@0 440 continue;
Chris@0 441 }
Chris@0 442 } else if (arg == "-t" || arg == "--transform") {
Chris@0 443 if (last || args[i+1].startsWith("-")) {
Chris@0 444 cerr << myname.toStdString() << ": argument expected for \""
Chris@0 445 << arg.toStdString() << "\" option" << endl;
Chris@0 446 cerr << helpStr.toStdString() << endl;
Chris@0 447 exit(2);
Chris@0 448 } else {
Chris@0 449 string transform = args[++i].toStdString();
Chris@0 450 if (requestedTransformFiles.find(transform) !=
Chris@0 451 requestedTransformFiles.end()) {
Chris@0 452 cerr << myname.toStdString() << ": NOTE: duplicate specification of transform file \"" << transform << "\" ignored" << endl;
Chris@0 453 } else {
Chris@0 454 requestedTransformFiles.insert(transform);
Chris@0 455 }
Chris@0 456 continue;
Chris@0 457 }
Chris@0 458 } else if (arg == "-T" || arg == "--transforms") {
Chris@0 459 if (last || args[i+1].startsWith("-")) {
Chris@0 460 cerr << myname.toStdString() << ": argument expected for \""
Chris@0 461 << arg.toStdString() << "\" option" << endl;
Chris@0 462 cerr << helpStr.toStdString() << endl;
Chris@0 463 exit(2);
Chris@0 464 } else {
Chris@0 465 string transform = args[++i].toStdString();
Chris@0 466 if (requestedTransformListFiles.find(transform) !=
Chris@0 467 requestedTransformListFiles.end()) {
Chris@0 468 cerr << myname.toStdString() << ": NOTE: duplicate specification of transform list file \"" << transform << "\" ignored" << endl;
Chris@0 469 } else {
Chris@0 470 requestedTransformListFiles.insert(transform);
Chris@0 471 }
Chris@0 472 continue;
Chris@0 473 }
Chris@0 474 } else if (arg == "-d" || arg == "--default") {
Chris@0 475 if (last || args[i+1].startsWith("-")) {
Chris@0 476 cerr << myname.toStdString() << ": argument expected for \""
Chris@0 477 << arg.toStdString() << "\" option" << endl;
Chris@0 478 cerr << helpStr.toStdString() << endl;
Chris@0 479 exit(2);
Chris@0 480 } else {
Chris@0 481 string deft = args[++i].toStdString();
Chris@0 482 if (requestedDefaultTransforms.find(deft) !=
Chris@0 483 requestedDefaultTransforms.end()) {
Chris@0 484 cerr << myname.toStdString() << ": NOTE: duplicate specification of default transform \"" << deft << "\" ignored" << endl;
Chris@0 485 } else {
Chris@0 486 requestedDefaultTransforms.insert(deft);
Chris@0 487 }
Chris@0 488 continue;
Chris@0 489 }
Chris@0 490 } else if (arg == "-S" || arg == "--summary") {
Chris@0 491 if (last || args[i+1].startsWith("-")) {
Chris@0 492 cerr << myname.toStdString() << ": argument expected for \""
Chris@0 493 << arg.toStdString() << "\" option" << endl;
Chris@0 494 cerr << helpStr.toStdString() << endl;
Chris@0 495 exit(2);
Chris@0 496 } else {
Chris@0 497 string summary = args[++i].toStdString();
Chris@0 498 requestedSummaryTypes.insert(summary);
Chris@0 499 continue;
Chris@0 500 }
Chris@0 501 } else if (arg == "--summary-only") {
Chris@0 502 summaryOnly = true;
Chris@0 503 continue;
Chris@0 504 } else if (arg == "--segments") {
Chris@0 505 if (last) {
Chris@0 506 cerr << myname.toStdString() << ": argument expected for \""
Chris@0 507 << arg.toStdString() << "\" option" << endl;
Chris@0 508 cerr << helpStr.toStdString() << endl;
Chris@0 509 exit(2);
Chris@0 510 } else {
Chris@0 511 string segmentSpec = args[++i].toStdString();
Chris@0 512 QStringList segmentStrs = QString(segmentSpec.c_str()).split(',');
Chris@0 513 for (int j = 0; j < segmentStrs.size(); ++j) {
Chris@0 514 bool good = false;
Chris@0 515 boundaries.insert(Vamp::RealTime::fromSeconds
Chris@0 516 (segmentStrs[j].toDouble(&good)));
Chris@0 517 if (!good) {
Chris@0 518 cerr << myname.toStdString() << ": segment boundaries must be numeric" << endl;
Chris@0 519 cerr << helpStr.toStdString() << endl;
Chris@0 520 exit(2);
Chris@0 521 }
Chris@0 522 }
Chris@0 523 }
Chris@0 524 } else if (arg == "-m" || arg == "--multiplex") {
Chris@0 525 multiplex = true;
Chris@0 526 continue;
Chris@0 527 } else if (arg == "-r" || arg == "--recursive") {
Chris@0 528 recursive = true;
Chris@0 529 continue;
Chris@116 530 } else if (arg == "-n" || arg == "--normalise") {
Chris@116 531 normalise = true;
Chris@116 532 continue;
Chris@21 533 } else if (arg == "-f" || arg == "--force") {
Chris@21 534 force = true;
Chris@21 535 continue;
Chris@0 536 } else if (arg == "-l" || arg == "--list") {
Chris@0 537 list = true;
Chris@0 538 continue;
Chris@0 539 } else if (arg == "-s" || arg == "--skeleton") {
Chris@0 540 if (last || args[i+1].startsWith("-")) {
Chris@0 541 cerr << myname.toStdString() << ": usage: "
Chris@0 542 << myname.toStdString() << " " << arg.toStdString()
Chris@0 543 << " <transform>" << endl;
Chris@0 544 cerr << helpStr.toStdString() << endl;
Chris@0 545 exit(2);
Chris@0 546 } else {
Chris@0 547 skeletonFor = args[++i];
Chris@0 548 continue;
Chris@0 549 }
Chris@0 550 } else {
Chris@0 551 otherArgs.push_back(args[i]);
Chris@0 552 }
Chris@0 553 }
Chris@0 554
Chris@0 555 if (list) {
Chris@0 556 if (!requestedWriterTags.empty() || skeletonFor != "") {
Chris@0 557 cerr << helpStr.toStdString() << endl;
Chris@0 558 exit(2);
Chris@0 559 }
Chris@0 560 listTransforms();
Chris@0 561 exit(0);
Chris@0 562 }
Chris@0 563 if (skeletonFor != "") {
Chris@0 564 if (!requestedWriterTags.empty()) {
Chris@0 565 cerr << helpStr.toStdString() << endl;
Chris@0 566 exit(2);
Chris@0 567 }
Chris@0 568 printSkeleton(skeletonFor);
Chris@0 569 exit(0);
Chris@0 570 }
Chris@0 571
Chris@0 572 if (requestedTransformFiles.empty() &&
Chris@0 573 requestedTransformListFiles.empty() &&
Chris@0 574 requestedDefaultTransforms.empty()) {
Chris@0 575 cerr << myname.toStdString()
Chris@0 576 << ": no transform(s) specified" << endl;
Chris@0 577 cerr << helpStr.toStdString() << endl;
Chris@0 578 exit(2);
Chris@0 579 }
Chris@0 580
Chris@0 581 if (requestedWriterTags.empty()) {
Chris@0 582 cerr << myname.toStdString()
Chris@0 583 << ": no writer(s) specified" << endl;
Chris@0 584 cerr << helpStr.toStdString() << endl;
Chris@0 585 exit(2);
Chris@0 586 }
Chris@0 587
Chris@0 588 if (!boundaries.empty()) {
Chris@0 589 if (requestedSummaryTypes.empty()) {
Chris@0 590 cerr << myname.toStdString()
Chris@0 591 << ": summary segment boundaries provided, but no summary type specified"
Chris@0 592 << endl;
Chris@0 593 cerr << helpStr.toStdString() << endl;
Chris@0 594 exit(2);
Chris@0 595 }
Chris@0 596 }
Chris@0 597
Chris@20 598 QSettings settings;
Chris@20 599
Chris@0 600 #ifdef HAVE_FFTW3
Chris@0 601 settings.beginGroup("FFTWisdom");
Chris@0 602 QString wisdom = settings.value("wisdom").toString();
Chris@0 603 if (wisdom != "") {
Chris@0 604 fftw_import_wisdom_from_string(wisdom.toLocal8Bit().data());
Chris@0 605 }
Chris@0 606 settings.endGroup();
Chris@0 607 #endif
Chris@0 608
Chris@20 609 settings.beginGroup("RDF");
Chris@20 610 if (!settings.contains("rdf-indices")) {
Chris@20 611 QStringList list;
Chris@20 612 list << "http://www.vamp-plugins.org/rdf/plugins/index.txt";
Chris@20 613 settings.setValue("rdf-indices", list);
Chris@20 614 }
Chris@20 615 settings.endGroup();
Chris@20 616
Chris@0 617 FeatureExtractionManager manager;
Chris@0 618
Chris@116 619 manager.setNormalise(normalise);
Chris@116 620
Chris@0 621 if (!requestedSummaryTypes.empty()) {
Chris@0 622 if (!manager.setSummaryTypes(requestedSummaryTypes,
Chris@0 623 boundaries)) {
Chris@0 624 cerr << myname.toStdString()
Chris@0 625 << ": failed to set requested summary types" << endl;
Chris@0 626 exit(1);
Chris@0 627 }
Chris@0 628 }
Chris@102 629
Chris@102 630 manager.setSummariesOnly(summaryOnly);
Chris@116 631
Chris@0 632 vector<FeatureWriter *> writers;
Chris@0 633
Chris@0 634 for (set<string>::const_iterator i = requestedWriterTags.begin();
Chris@0 635 i != requestedWriterTags.end(); ++i) {
Chris@0 636
Chris@0 637 FeatureWriter *writer = FeatureWriterFactory::createWriter(*i);
Chris@0 638
Chris@0 639 if (!writer) {
Chris@0 640 cerr << myname.toStdString() << ": unknown feature writer \""
Chris@0 641 << *i << "\"" << endl;
Chris@0 642 cerr << helpStr.toStdString() << endl;
Chris@0 643 exit(2);
Chris@0 644 }
Chris@0 645
Chris@0 646 map<string, string> writerArgs;
Chris@0 647 FeatureWriter::ParameterList pl(writer->getSupportedParameters());
Chris@0 648
Chris@95 649 for (int k = 0; k < (int)pl.size(); ++k) {
Chris@0 650
Chris@0 651 string argbase = pl[k].name;
Chris@0 652 QString literal = QString("--%1-%2")
Chris@0 653 .arg(i->c_str()).arg(argbase.c_str());
Chris@0 654
Chris@95 655 for (int j = 0; j < (int)otherArgs.size(); ) {
Chris@0 656
Chris@0 657 if (otherArgs[j] != literal) {
Chris@0 658 ++j;
Chris@0 659 continue;
Chris@0 660 }
Chris@0 661
Chris@0 662 otherArgs.removeAt(j);
Chris@0 663
Chris@0 664 if (pl[k].hasArg) {
Chris@0 665 if (j < otherArgs.size()) {
Chris@0 666 writerArgs[argbase] = otherArgs[j].toStdString();
Chris@0 667 otherArgs.removeAt(j);
Chris@0 668 } else {
Chris@0 669 cerr << myname.toStdString() << ": "
Chris@0 670 << "argument required for \""
Chris@0 671 << literal.toStdString() << "\" option"
Chris@0 672 << endl;
Chris@0 673 cerr << helpStr.toStdString() << endl;
Chris@0 674 exit(2);
Chris@0 675 }
Chris@0 676 } else {
Chris@0 677 writerArgs[argbase] = "";
Chris@0 678 }
Chris@0 679 }
Chris@0 680 }
Chris@0 681
Chris@0 682 writer->setParameters(writerArgs);
Chris@0 683
Chris@0 684 writers.push_back(writer);
Chris@0 685 }
Chris@0 686
Chris@0 687 for (int i = 0; i < otherArgs.size(); ++i) {
Chris@0 688 if (otherArgs[i].startsWith("-")) {
Chris@0 689 cerr << myname.toStdString() << ": unknown option \""
Chris@0 690 << otherArgs[i].toStdString() << "\"" << endl;
Chris@0 691 cerr << helpStr.toStdString() << endl;
Chris@0 692 exit(2);
Chris@0 693 }
Chris@0 694 }
Chris@0 695
Chris@0 696 if (otherArgs.empty()) {
Chris@0 697 cerr << myname.toStdString() << ": no input(s) specified" << endl;
Chris@0 698 cerr << helpStr.toStdString() << endl;
Chris@0 699 exit(2);
Chris@0 700 }
Chris@0 701
Chris@0 702 for (set<string>::const_iterator i = requestedTransformListFiles.begin();
Chris@0 703 i != requestedTransformListFiles.end(); ++i) {
Chris@0 704 PlaylistFileReader reader(i->c_str());
Chris@0 705 if (reader.isOK()) {
Chris@0 706 vector<QString> files = reader.load();
Chris@95 707 for (int j = 0; j < (int)files.size(); ++j) {
Chris@0 708 requestedTransformFiles.insert(files[j].toStdString());
Chris@0 709 }
Chris@0 710 } else {
Chris@0 711 cerr << myname.toStdString() << ": failed to read template list file \"" << *i << "\"" << endl;
Chris@0 712 exit(2);
Chris@0 713 }
Chris@0 714 }
Chris@0 715
Chris@0 716 QStringList sources;
Chris@0 717 if (!recursive) {
Chris@0 718 sources = otherArgs;
Chris@0 719 } else {
Chris@0 720 for (QStringList::const_iterator i = otherArgs.begin();
Chris@0 721 i != otherArgs.end(); ++i) {
Chris@0 722 if (QDir(*i).exists()) {
Chris@0 723 cerr << "Directory found and recursive flag set, scanning for audio files..." << endl;
Chris@0 724 int found = 0;
Chris@0 725 findSourcesRecursive(*i, sources, found);
Chris@0 726 cerr << "\rDone, found " << found << " supported audio file(s) " << endl;
Chris@0 727 } else {
Chris@0 728 sources.push_back(*i);
Chris@0 729 }
Chris@0 730 }
Chris@0 731 }
Chris@0 732
Chris@111 733 sources = expandPlaylists(sources);
Chris@117 734
Chris@28 735 bool good = true;
Chris@45 736 QSet<QString> badSources;
Chris@28 737
Chris@0 738 for (QStringList::const_iterator i = sources.begin();
Chris@0 739 i != sources.end(); ++i) {
Chris@0 740 try {
Chris@106 741 manager.addSource(*i, multiplex);
Chris@22 742 } catch (const std::exception &e) {
Chris@45 743 badSources.insert(*i);
Chris@21 744 cerr << "ERROR: Failed to process file \"" << i->toStdString()
Chris@21 745 << "\": " << e.what() << endl;
Chris@22 746 if (force) {
Chris@22 747 // print a note only if we have more files to process
Chris@22 748 QStringList::const_iterator j = i;
Chris@22 749 if (++j != sources.end()) {
Chris@22 750 cerr << "NOTE: \"--force\" option was provided, continuing (more errors may occur)" << endl;
Chris@22 751 }
Chris@22 752 } else {
Chris@45 753 cerr << "NOTE: If you want to continue with processing any further files after an" << endl
Chris@45 754 << "error like this, use the --force option" << endl;
Chris@28 755 good = false;
Chris@22 756 break;
Chris@22 757 }
Chris@0 758 }
Chris@0 759 }
Chris@45 760
Chris@45 761 if (good) {
Chris@45 762
Chris@45 763 bool haveFeatureExtractor = false;
Chris@45 764
Chris@45 765 for (set<string>::const_iterator i = requestedTransformFiles.begin();
Chris@45 766 i != requestedTransformFiles.end(); ++i) {
Chris@45 767 if (manager.addFeatureExtractorFromFile(i->c_str(), writers)) {
Chris@45 768 haveFeatureExtractor = true;
Chris@120 769 } else {
Chris@120 770 cerr << "ERROR: Failed to add feature extractor from transform file \"" << *i << "\"" << endl;
Chris@120 771 good = false;
Chris@45 772 }
Chris@45 773 }
Chris@45 774
Chris@45 775 for (set<string>::const_iterator i = requestedDefaultTransforms.begin();
Chris@45 776 i != requestedDefaultTransforms.end(); ++i) {
Chris@45 777 if (manager.addDefaultFeatureExtractor(i->c_str(), writers)) {
Chris@45 778 haveFeatureExtractor = true;
Chris@120 779 } else {
Chris@120 780 cerr << "ERROR: Failed to add default feature extractor for transform \"" << *i << "\"" << endl;
Chris@120 781 good = false;
Chris@45 782 }
Chris@45 783 }
Chris@45 784
Chris@45 785 if (!haveFeatureExtractor) {
Chris@45 786 cerr << myname.toStdString() << ": no feature extractors added" << endl;
Chris@45 787 good = false;
Chris@45 788 }
Chris@45 789 }
Chris@45 790
Chris@45 791 if (good) {
Chris@106 792 QStringList goodSources;
Chris@106 793 foreach (QString source, sources) {
Chris@106 794 if (!badSources.contains(source)) {
Chris@106 795 goodSources.push_back(source);
Chris@106 796 }
Chris@106 797 }
Chris@106 798 if (multiplex) {
Chris@45 799 try {
Chris@106 800 manager.extractFeaturesMultiplexed(goodSources);
Chris@45 801 } catch (const std::exception &e) {
Chris@106 802 cerr << "ERROR: Feature extraction failed: "
Chris@106 803 << e.what() << endl;
Chris@106 804 }
Chris@106 805 } else {
Chris@106 806 for (QStringList::const_iterator i = goodSources.begin();
Chris@106 807 i != goodSources.end(); ++i) {
Chris@106 808 std::cerr << "Extracting features for: \"" << i->toStdString()
Chris@106 809 << "\"" << std::endl;
Chris@106 810 try {
Chris@112 811 manager.extractFeatures(*i);
Chris@106 812 } catch (const std::exception &e) {
Chris@106 813 cerr << "ERROR: Feature extraction failed for \""
Chris@106 814 << i->toStdString() << "\": " << e.what() << endl;
Chris@106 815 if (force) {
Chris@106 816 // print a note only if we have more files to process
Chris@106 817 QStringList::const_iterator j = i;
Chris@106 818 if (++j != sources.end()) {
Chris@106 819 cerr << "NOTE: \"--force\" option was provided, continuing (more errors may occur)" << endl;
Chris@106 820 }
Chris@106 821 } else {
Chris@106 822 cerr << "NOTE: If you want to continue with processing any further files after an" << endl
Chris@106 823 << "error like this, use the --force option" << endl;
Chris@106 824 good = false;
Chris@106 825 break;
Chris@45 826 }
Chris@45 827 }
Chris@45 828 }
Chris@45 829 }
Chris@45 830 }
Chris@0 831
Chris@95 832 for (int i = 0; i < (int)writers.size(); ++i) delete writers[i];
Chris@0 833
Chris@0 834 #ifdef HAVE_FFTW3
Chris@0 835 settings.beginGroup("FFTWisdom");
Chris@0 836 char *cwisdom = fftw_export_wisdom_to_string();
Chris@0 837 if (cwisdom) {
Chris@0 838 settings.setValue("wisdom", cwisdom);
Chris@0 839 fftw_free(cwisdom);
Chris@0 840 }
Chris@0 841 settings.endGroup();
Chris@0 842 #endif
Chris@0 843
Chris@0 844 TempDirectory::getInstance()->cleanup();
Chris@0 845
Chris@28 846 if (good) return 0;
Chris@28 847 else return 1;
Chris@0 848 }
Chris@0 849
Chris@0 850