annotate runner/main.cpp @ 152:db83ea0e102d jams

Make the JSON well-formed (though still empty), except in the case where we are asked to write more than one file's features to the same output file or stdout
author Chris Cannam
date Tue, 14 Oct 2014 12:35:19 +0100
parents b3d73c08b6ce
children 859d8ec60e06
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@127 140 static bool
Chris@127 141 isVersionNewerThan(QString a, QString b) // from VersionTester in svapp
Chris@127 142 {
Chris@127 143 QRegExp re("[._-]");
Chris@127 144 QStringList alist = a.split(re, QString::SkipEmptyParts);
Chris@127 145 QStringList blist = b.split(re, QString::SkipEmptyParts);
Chris@127 146 int ae = alist.size();
Chris@127 147 int be = blist.size();
Chris@127 148 int e = std::max(ae, be);
Chris@127 149 for (int i = 0; i < e; ++i) {
Chris@127 150 int an = 0, bn = 0;
Chris@127 151 if (i < ae) {
Chris@127 152 an = alist[i].toInt();
Chris@127 153 if (an == 0 && alist[i] != "0") {
Chris@127 154 an = -1; // non-numeric field -> "-pre1" etc
Chris@127 155 }
Chris@127 156 }
Chris@127 157 if (i < be) {
Chris@127 158 bn = blist[i].toInt();
Chris@127 159 if (bn == 0 && blist[i] != "0") {
Chris@127 160 bn = -1;
Chris@127 161 }
Chris@127 162 }
Chris@127 163 if (an < bn) return false;
Chris@127 164 if (an > bn) return true;
Chris@127 165 }
Chris@127 166 return false;
Chris@127 167 }
Chris@127 168
Chris@127 169 static int
Chris@127 170 checkMinVersion(QString myname, QString v)
Chris@127 171 {
Chris@127 172 if (v == RUNNER_VERSION) {
Chris@127 173 return 0;
Chris@127 174 } else if (isVersionNewerThan(RUNNER_VERSION, v)) {
Chris@127 175 return 0;
Chris@127 176 } else {
Chris@127 177 cerr << myname << ": version "
Chris@127 178 << RUNNER_VERSION << " is less than requested min version "
Chris@127 179 << v << ", failing" << endl;
Chris@127 180 return 1;
Chris@127 181 }
Chris@127 182 }
Chris@127 183
Chris@125 184 void printUsage(QString myname)
Chris@0 185 {
Chris@0 186 cerr << endl;
Chris@2 187 cerr << "Sonic Annotator v" << RUNNER_VERSION << endl;
Chris@0 188 cerr << "A utility for batch feature extraction from audio files." << endl;
Chris@0 189 cerr << "Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London." << endl;
Chris@95 190 cerr << "Copyright 2007-2014 Queen Mary, University of London." << endl;
Chris@0 191 cerr << endl;
Chris@0 192 cerr << "This program is free software. You may redistribute copies of it under the" << endl;
Chris@0 193 cerr << "terms of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>." << endl;
Chris@0 194 cerr << "This program is supplied with NO WARRANTY, to the extent permitted by law." << endl;
Chris@0 195 cerr << endl;
Chris@125 196 cerr << "Usage: " << endl;
Chris@127 197 cerr << " " << myname
Chris@125 198 << " [-mrnf] -t transform.ttl [..] -w <writer> [..] <audio> [..]" << endl;
Chris@127 199 cerr << " " << myname
Chris@125 200 << " [-mrnf] -T translist.txt [..] -w <writer> [..] <audio> [..]" << endl;
Chris@127 201 cerr << " " << myname
Chris@125 202 << " [-mrnf] -d <plugin> [..] -w <writer> [..] <audio> [...]" << endl;
Chris@127 203 cerr << " " << myname
Chris@0 204 << " -s <transform>" << endl;
Chris@127 205 cerr << " " << myname
Chris@46 206 << " [-lhv]" << endl;
Chris@0 207 cerr << endl;
Chris@0 208 cerr << "Where <audio> is an audio file or URL to use as input: either a local file" << endl;
Chris@125 209 cerr << "path, local \"file://\" URL, or remote \"http://\" or \"ftp://\" URL;" << endl;
Chris@125 210 cerr << "and <plugin> is a plugin output identified as vamp:libname:plugin:output." << endl;
Chris@0 211 cerr << endl;
Chris@125 212 }
Chris@125 213
Chris@142 214 void printHelp(QString myname, QString w)
Chris@125 215 {
Chris@142 216 std::string writer = w.toStdString();
Chris@142 217
Chris@125 218 printUsage(myname);
Chris@0 219
Chris@0 220 QString extensions = AudioFileReaderFactory::getKnownExtensions();
Chris@0 221 QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
Chris@0 222 if (!extlist.empty()) {
Chris@0 223 cerr << "The following audio file extensions are recognised:" << endl;
Chris@0 224 cerr << " ";
Chris@0 225 int c = 2;
Chris@0 226 for (int i = 0; i < extlist.size(); ++i) {
Chris@0 227 QString ext = extlist[i];
Chris@0 228 if (ext.startsWith("*.")) ext = ext.right(ext.length()-2);
Chris@0 229 c += ext.length() + 2;
Chris@0 230 if (c >= 80) {
Chris@0 231 cerr << "\n ";
Chris@0 232 c -= 78;
Chris@0 233 }
Chris@127 234 cerr << ext;
Chris@0 235 if (i + 1 == extlist.size()) cerr << ".";
Chris@0 236 else cerr << ", ";
Chris@0 237 }
Chris@0 238 cerr << endl;
Chris@0 239 }
Chris@0 240
Chris@0 241 cerr << "Playlist files in M3U format are also supported." << endl;
Chris@0 242 cerr << endl;
Chris@125 243
Chris@125 244 set<string> writers = FeatureWriterFactory::getWriterTags();
Chris@142 245
Chris@142 246 QString writerText = "Supported writer types are: ";
Chris@0 247 for (set<string>::const_iterator i = writers.begin();
Chris@0 248 i != writers.end(); ) {
Chris@142 249 writerText += i->c_str();
Chris@142 250 if (++i != writers.end()) writerText += ", ";
Chris@142 251 else writerText += ".";
Chris@0 252 }
Chris@142 253 writerText = wrap(writerText, 56, 22);
Chris@0 254
Chris@142 255 if (writer == "" || writers.find(writer) == writers.end()) {
Chris@0 256
Chris@142 257 cerr << "Transformation options:" << endl;
Chris@142 258 cerr << endl;
Chris@142 259 cerr << " -t, --transform <T> Apply transform described in transform file <T> to" << endl;
Chris@142 260 cerr << " all input audio files. You may supply this option" << endl;
Chris@142 261 cerr << " multiple times. You must supply this option or -T at" << endl;
Chris@142 262 cerr << " least once for any work to be done. Transform format" << endl;
Chris@142 263 cerr << " may be SV transform XML or Vamp transform RDF/Turtle." << endl;
Chris@142 264 cerr << " See accompanying documentation for transform examples." << endl;
Chris@142 265 cerr << endl;
Chris@142 266 cerr << " -T, --transforms <T> Apply all transforms described in transform files" << endl;
Chris@142 267 cerr << " whose names are listed in text file <T>. You may supply" << endl;
Chris@142 268 cerr << " this option multiple times." << endl;
Chris@142 269 cerr << endl;
Chris@142 270 cerr << " -d, --default <I> Apply the default transform for transform id <I>. This" << endl;
Chris@142 271 cerr << " is equivalent to generating a skeleton transform for this" << endl;
Chris@142 272 cerr << " id (using the -s option, below) and then applying that," << endl;
Chris@142 273 cerr << " unmodified, with the -t option in the normal way. Note" << endl;
Chris@142 274 cerr << " that results may vary, as the implementation's default" << endl;
Chris@142 275 cerr << " processing parameters are not guaranteed. Do not use" << endl;
Chris@142 276 cerr << " this in production systems. You may supply this option" << endl;
Chris@142 277 cerr << " multiple times, and mix it with -t and -T." << endl;
Chris@142 278 cerr << endl;
Chris@142 279 cerr << " -w, --writer <W> Write output using writer type <W>." << endl;
Chris@142 280 cerr << " " << writerText << endl;
Chris@142 281 cerr << " You may supply this option multiple times. You must" << endl;
Chris@142 282 cerr << " supply this option at least once for any work to be done." << endl;
Chris@142 283 cerr << endl;
Chris@142 284 cerr << " -S, --summary <S> In addition to the result features, write summary feature" << endl;
Chris@142 285 cerr << " of summary type <S>." << endl;
Chris@142 286 cerr << " Supported summary types are min, max, mean, median, mode," << endl;
Chris@142 287 cerr << " sum, variance, sd, count." << endl;
Chris@142 288 cerr << " You may supply this option multiple times." << endl;
Chris@142 289 cerr << endl;
Chris@142 290 cerr << " --summary-only Write only summary features; do not write the regular" << endl;
Chris@142 291 cerr << " result features." << endl;
Chris@142 292 cerr << endl;
Chris@142 293 cerr << " --segments <A>,<B>[,...]" << endl;
Chris@142 294 cerr << " Summarise in segments, with segment boundaries" << endl;
Chris@142 295 cerr << " at A, B, ... seconds." << endl;
Chris@142 296 cerr << endl;
Chris@142 297 cerr << " -m, --multiplex If multiple input audio files are given, use mono" << endl;
Chris@142 298 cerr << " mixdowns of all files as the input channels for a single" << endl;
Chris@142 299 cerr << " invocation of each transform, instead of running the" << endl;
Chris@142 300 cerr << " transform against all files separately. The first file" << endl;
Chris@142 301 cerr << " will be used for output reference name and sample rate." << endl;
Chris@142 302 cerr << endl;
Chris@142 303 cerr << " -r, --recursive If any of the <audio> arguments is found to be a local" << endl;
Chris@142 304 cerr << " directory, search the tree starting at that directory" << endl;
Chris@142 305 cerr << " for all supported audio files and take all of those as" << endl;
Chris@142 306 cerr << " input instead." << endl;
Chris@142 307 cerr << endl;
Chris@142 308 cerr << " -n, --normalise Normalise input audio files to signal absolute max = 1.f." << endl;
Chris@142 309 cerr << endl;
Chris@142 310 cerr << " -f, --force Continue with subsequent files following an error." << endl;
Chris@142 311 cerr << endl;
Chris@142 312 cerr << "Housekeeping options:" << endl;
Chris@142 313 cerr << endl;
Chris@144 314 cerr << " -l, --list List available transform ids to standard output." << endl;
Chris@144 315 cerr << " --list-writers List supported writer types to standard output." << endl;
Chris@144 316 cerr << " --list-formats List supported input audio formats to standard output." << endl;
Chris@142 317 cerr << endl;
Chris@142 318 cerr << " -s, --skeleton <I> Generate a skeleton transform file for transform id <I>" << endl;
Chris@142 319 cerr << " and write it to standard output." << endl;
Chris@142 320 cerr << endl;
Chris@142 321 cerr << " -v, --version Show the version number and exit." << endl;
Chris@142 322 cerr << endl;
Chris@142 323 cerr << " --minversion <V> Exit with successful return code if the version of" << endl;
Chris@142 324 cerr << " " << myname << " is at least <V>, failure otherwise." << endl;
Chris@142 325 cerr << " For scripts that depend on certain option support." << endl;
Chris@142 326 cerr << endl;
Chris@142 327 cerr << " -h, --help Show help." << endl;
Chris@142 328 cerr << " -h, --help <W> Show help for writer type W." << endl;
Chris@142 329 cerr << " " << writerText << endl;
Chris@142 330
Chris@142 331 cerr << endl;
Chris@142 332 cerr << "If no -w (or --writer) options are supplied, either the -l -s -v or -h option" << endl;
Chris@142 333 cerr << "(or long equivalent) must be given instead." << endl;
Chris@142 334
Chris@142 335 } else {
Chris@142 336
Chris@142 337 FeatureWriter *w = FeatureWriterFactory::createWriter(writer);
Chris@0 338 if (!w) {
Chris@142 339 cerr << " (Internal error: failed to create writer of known type \""
Chris@142 340 << writer << "\")" << endl;
Chris@142 341 return;
Chris@0 342 }
Chris@144 343 cerr << "Feature writer \"" << writer << "\":" << endl << endl;
Chris@144 344 cerr << " " << wrap(w->getDescription().c_str(), 76, 2) << endl << endl;
Chris@142 345 cerr << "Additional options for writer type \"" << writer << "\":" << endl;
Chris@142 346 cerr << endl;
Chris@0 347 FeatureWriter::ParameterList params = w->getSupportedParameters();
Chris@0 348 delete w;
Chris@0 349 if (params.empty()) {
Chris@144 350 cerr << " No additional options are available for this writer." << endl << endl;
Chris@142 351 return;
Chris@0 352 }
Chris@0 353 for (FeatureWriter::ParameterList::const_iterator j = params.begin();
Chris@0 354 j != params.end(); ++j) {
Chris@142 355 cerr << " --" << writer << "-" << j->name << " ";
Chris@142 356 int spaceage = 16 - int(writer.length()) - int(j->name.length());
Chris@0 357 if (j->hasArg) { cerr << "<X> "; spaceage -= 4; }
Chris@0 358 for (int k = 0; k < spaceage; ++k) cerr << " ";
Chris@0 359 QString s(j->description.c_str());
Chris@0 360 s = wrap(s, 56, 22);
Chris@127 361 cerr << s << endl;
Chris@0 362 }
Chris@0 363 }
Chris@0 364
Chris@0 365 cerr << endl;
Chris@0 366 }
Chris@0 367
Chris@0 368 void
Chris@0 369 listTransforms()
Chris@0 370 {
Chris@0 371 TransformList transforms =
Chris@0 372 TransformFactory::getInstance()->getAllTransformDescriptions();
Chris@0 373
Chris@0 374 for (TransformList::const_iterator iter = transforms.begin();
Chris@0 375 iter != transforms.end(); ++iter) {
Chris@0 376 const TransformDescription &transform = *iter;
Chris@0 377 if (transform.type == TransformDescription::Analysis) {
Chris@127 378 cout << transform.identifier << endl;
Chris@0 379 }
Chris@0 380 }
Chris@0 381 }
Chris@0 382
Chris@0 383 void
Chris@0 384 printSkeleton(QString id)
Chris@0 385 {
Chris@0 386 Transform transform =
Chris@0 387 TransformFactory::getInstance()->getDefaultTransformFor(id);
Chris@0 388 cout << "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> ." << endl
Chris@0 389 << "@prefix vamp: <http://purl.org/ontology/vamp/> ." << endl
Chris@0 390 << "@prefix : <#> ." << endl << endl;
Chris@0 391 QString rdf = RDFTransformFactory::writeTransformToRDF
Chris@0 392 (transform, ":transform");
Chris@127 393 cout << rdf;
Chris@0 394 }
Chris@0 395
Chris@0 396 void
Chris@0 397 findSourcesRecursive(QString dirname, QStringList &addTo, int &found)
Chris@0 398 {
Chris@0 399 QDir dir(dirname);
Chris@0 400
Chris@0 401 QString printable = dir.dirName().left(20);
Chris@127 402 cerr << "\rScanning \"" << printable << "\"..."
Chris@127 403 << QString(" ").left(20 - printable.length())
Chris@0 404 << " [" << found << " audio file(s)]";
Chris@0 405
Chris@0 406 QString extensions = AudioFileReaderFactory::getKnownExtensions();
Chris@0 407 QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
Chris@0 408
Chris@0 409 QStringList files = dir.entryList
Chris@0 410 (extlist, QDir::Files | QDir::Readable);
Chris@0 411 for (int i = 0; i < files.size(); ++i) {
Chris@0 412 addTo.push_back(dir.filePath(files[i]));
Chris@0 413 ++found;
Chris@0 414 }
Chris@0 415
Chris@0 416 QStringList subdirs = dir.entryList
Chris@0 417 (QStringList(), QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
Chris@0 418 for (int i = 0; i < subdirs.size(); ++i) {
Chris@0 419 findSourcesRecursive(dir.filePath(subdirs[i]), addTo, found);
Chris@0 420 }
Chris@0 421 }
Chris@0 422
Chris@111 423 QStringList
Chris@111 424 expandPlaylists(QStringList sources)
Chris@111 425 {
Chris@111 426 QStringList expanded;
Chris@111 427 foreach (QString path, sources) {
Chris@111 428 if (QFileInfo(path).suffix().toLower() == "m3u") {
Chris@111 429 ProgressPrinter retrievalProgress("Opening playlist file...");
Chris@111 430 FileSource source(path, &retrievalProgress);
Chris@111 431 if (!source.isAvailable()) {
Chris@117 432 // Don't fail or throw an exception here, just keep
Chris@117 433 // the file in the list -- it will be tested again
Chris@117 434 // when adding it as a source and that's the proper
Chris@117 435 // time to fail. All we're concluding here is that it
Chris@117 436 // isn't a valid playlist
Chris@117 437 expanded.push_back(path);
Chris@117 438 continue;
Chris@111 439 }
Chris@111 440 source.waitForData();
Chris@111 441 PlaylistFileReader reader(source);
Chris@111 442 if (reader.isOK()) {
Chris@111 443 vector<QString> files = reader.load();
Chris@111 444 for (int i = 0; i < (int)files.size(); ++i) {
Chris@111 445 expanded.push_back(files[i]);
Chris@111 446 }
Chris@111 447 }
Chris@111 448 } else {
Chris@111 449 // not a playlist
Chris@111 450 expanded.push_back(path);
Chris@111 451 }
Chris@111 452 }
Chris@111 453 return expanded;
Chris@111 454 }
Chris@0 455
Chris@0 456 int main(int argc, char **argv)
Chris@0 457 {
Chris@0 458 QCoreApplication application(argc, argv);
Chris@0 459
Chris@0 460 QCoreApplication::setOrganizationName("QMUL");
Chris@0 461 QCoreApplication::setOrganizationDomain("qmul.ac.uk");
Chris@0 462 QCoreApplication::setApplicationName("Sonic Annotator");
Chris@0 463
Chris@0 464 QStringList args = application.arguments();
Chris@0 465 set<string> requestedWriterTags;
Chris@0 466 set<string> requestedTransformFiles;
Chris@0 467 set<string> requestedTransformListFiles;
Chris@0 468 set<string> requestedDefaultTransforms;
Chris@0 469 set<string> requestedSummaryTypes;
Chris@21 470 bool force = false;
Chris@106 471 bool multiplex = false;
Chris@0 472 bool recursive = false;
Chris@116 473 bool normalise = false;
Chris@0 474 bool list = false;
Chris@144 475 bool listWriters = false;
Chris@144 476 bool listFormats = false;
Chris@0 477 bool summaryOnly = false;
Chris@0 478 QString skeletonFor = "";
Chris@127 479 QString minVersion = "";
Chris@0 480 QString myname = args[0];
Chris@0 481 myname = QFileInfo(myname).baseName();
Chris@0 482 QStringList otherArgs;
Chris@0 483 Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries boundaries;
Chris@0 484
Chris@0 485 QString helpStr = myname + ": use -h or --help option for help";
Chris@0 486
Chris@0 487 for (int i = 1; i < args.size(); ++i) {
Chris@0 488
Chris@0 489 QString arg = args[i];
Chris@0 490 bool last = ((i + 1) == args.size());
Chris@0 491
Chris@0 492 if (arg == "-h" || arg == "--help" || arg == "-?") {
Chris@142 493 QString writer;
Chris@142 494 if (!last) {
Chris@142 495 writer = args[i+1];
Chris@142 496 }
Chris@142 497 printHelp(myname, writer);
Chris@125 498 return 0;
Chris@0 499 }
Chris@0 500
Chris@46 501 if (arg == "-v" || arg == "--version") {
Chris@46 502 std::cout << RUNNER_VERSION << std::endl;
Chris@46 503 return 0;
Chris@46 504 }
Chris@46 505
Chris@0 506 if (arg == "-w" || arg == "--writer") {
Chris@0 507 if (last || args[i+1].startsWith("-")) {
Chris@127 508 cerr << myname << ": argument expected for \""
Chris@127 509 << arg << "\" option" << endl;
Chris@127 510 cerr << helpStr << endl;
Chris@0 511 exit(2);
Chris@0 512 } else {
Chris@0 513 string tag = args[++i].toStdString();
Chris@0 514 if (requestedWriterTags.find(tag) != requestedWriterTags.end()) {
Chris@127 515 cerr << myname << ": NOTE: duplicate specification of writer type \"" << tag << "\" ignored" << endl;
Chris@0 516 } else {
Chris@0 517 requestedWriterTags.insert(tag);
Chris@0 518 }
Chris@0 519 continue;
Chris@0 520 }
Chris@0 521 } else if (arg == "-t" || arg == "--transform") {
Chris@0 522 if (last || args[i+1].startsWith("-")) {
Chris@127 523 cerr << myname << ": argument expected for \""
Chris@127 524 << arg << "\" option" << endl;
Chris@127 525 cerr << helpStr << endl;
Chris@0 526 exit(2);
Chris@0 527 } else {
Chris@0 528 string transform = args[++i].toStdString();
Chris@0 529 if (requestedTransformFiles.find(transform) !=
Chris@0 530 requestedTransformFiles.end()) {
Chris@127 531 cerr << myname << ": NOTE: duplicate specification of transform file \"" << transform << "\" ignored" << endl;
Chris@0 532 } else {
Chris@0 533 requestedTransformFiles.insert(transform);
Chris@0 534 }
Chris@0 535 continue;
Chris@0 536 }
Chris@0 537 } else if (arg == "-T" || arg == "--transforms") {
Chris@0 538 if (last || args[i+1].startsWith("-")) {
Chris@127 539 cerr << myname << ": argument expected for \""
Chris@127 540 << arg << "\" option" << endl;
Chris@127 541 cerr << helpStr << endl;
Chris@0 542 exit(2);
Chris@0 543 } else {
Chris@0 544 string transform = args[++i].toStdString();
Chris@0 545 if (requestedTransformListFiles.find(transform) !=
Chris@0 546 requestedTransformListFiles.end()) {
Chris@127 547 cerr << myname << ": NOTE: duplicate specification of transform list file \"" << transform << "\" ignored" << endl;
Chris@0 548 } else {
Chris@0 549 requestedTransformListFiles.insert(transform);
Chris@0 550 }
Chris@0 551 continue;
Chris@0 552 }
Chris@0 553 } else if (arg == "-d" || arg == "--default") {
Chris@0 554 if (last || args[i+1].startsWith("-")) {
Chris@127 555 cerr << myname << ": argument expected for \""
Chris@127 556 << arg << "\" option" << endl;
Chris@127 557 cerr << helpStr << endl;
Chris@0 558 exit(2);
Chris@0 559 } else {
Chris@0 560 string deft = args[++i].toStdString();
Chris@0 561 if (requestedDefaultTransforms.find(deft) !=
Chris@0 562 requestedDefaultTransforms.end()) {
Chris@127 563 cerr << myname << ": NOTE: duplicate specification of default transform \"" << deft << "\" ignored" << endl;
Chris@0 564 } else {
Chris@0 565 requestedDefaultTransforms.insert(deft);
Chris@0 566 }
Chris@0 567 continue;
Chris@0 568 }
Chris@0 569 } else if (arg == "-S" || arg == "--summary") {
Chris@0 570 if (last || args[i+1].startsWith("-")) {
Chris@127 571 cerr << myname << ": argument expected for \""
Chris@127 572 << arg << "\" option" << endl;
Chris@127 573 cerr << helpStr << endl;
Chris@0 574 exit(2);
Chris@0 575 } else {
Chris@0 576 string summary = args[++i].toStdString();
Chris@0 577 requestedSummaryTypes.insert(summary);
Chris@0 578 continue;
Chris@0 579 }
Chris@0 580 } else if (arg == "--summary-only") {
Chris@0 581 summaryOnly = true;
Chris@0 582 continue;
Chris@0 583 } else if (arg == "--segments") {
Chris@0 584 if (last) {
Chris@127 585 cerr << myname << ": argument expected for \""
Chris@127 586 << arg << "\" option" << endl;
Chris@127 587 cerr << helpStr << endl;
Chris@0 588 exit(2);
Chris@0 589 } else {
Chris@0 590 string segmentSpec = args[++i].toStdString();
Chris@0 591 QStringList segmentStrs = QString(segmentSpec.c_str()).split(',');
Chris@0 592 for (int j = 0; j < segmentStrs.size(); ++j) {
Chris@0 593 bool good = false;
Chris@0 594 boundaries.insert(Vamp::RealTime::fromSeconds
Chris@0 595 (segmentStrs[j].toDouble(&good)));
Chris@0 596 if (!good) {
Chris@127 597 cerr << myname << ": segment boundaries must be numeric" << endl;
Chris@127 598 cerr << helpStr << endl;
Chris@0 599 exit(2);
Chris@0 600 }
Chris@0 601 }
Chris@0 602 }
Chris@0 603 } else if (arg == "-m" || arg == "--multiplex") {
Chris@0 604 multiplex = true;
Chris@0 605 continue;
Chris@0 606 } else if (arg == "-r" || arg == "--recursive") {
Chris@0 607 recursive = true;
Chris@0 608 continue;
Chris@116 609 } else if (arg == "-n" || arg == "--normalise") {
Chris@116 610 normalise = true;
Chris@116 611 continue;
Chris@21 612 } else if (arg == "-f" || arg == "--force") {
Chris@21 613 force = true;
Chris@21 614 continue;
Chris@144 615 } else if (arg == "--list-writers") {
Chris@144 616 listWriters = true;
Chris@144 617 continue;
Chris@144 618 } else if (arg == "--list-formats") {
Chris@144 619 listFormats = true;
Chris@144 620 continue;
Chris@0 621 } else if (arg == "-l" || arg == "--list") {
Chris@0 622 list = true;
Chris@0 623 continue;
Chris@127 624 } else if (arg == "--minversion") {
Chris@127 625 if (last || args[i+1].startsWith("-")) {
Chris@127 626 cerr << myname << ": usage: "
Chris@127 627 << myname << " " << arg << " <version>" << endl;
Chris@127 628 cerr << helpStr << endl;
Chris@127 629 exit(2);
Chris@127 630 }
Chris@127 631 minVersion = args[++i];
Chris@127 632 continue;
Chris@0 633 } else if (arg == "-s" || arg == "--skeleton") {
Chris@0 634 if (last || args[i+1].startsWith("-")) {
Chris@127 635 cerr << myname << ": usage: "
Chris@127 636 << myname << " " << arg
Chris@0 637 << " <transform>" << endl;
Chris@127 638 cerr << helpStr << endl;
Chris@0 639 exit(2);
Chris@0 640 } else {
Chris@0 641 skeletonFor = args[++i];
Chris@0 642 continue;
Chris@0 643 }
Chris@0 644 } else {
Chris@0 645 otherArgs.push_back(args[i]);
Chris@0 646 }
Chris@0 647 }
Chris@0 648
Chris@0 649 if (list) {
Chris@0 650 if (!requestedWriterTags.empty() || skeletonFor != "") {
Chris@127 651 cerr << helpStr << endl;
Chris@0 652 exit(2);
Chris@0 653 }
Chris@0 654 listTransforms();
Chris@0 655 exit(0);
Chris@0 656 }
Chris@144 657 if (listWriters) {
Chris@144 658 if (!requestedWriterTags.empty() || skeletonFor != "") {
Chris@144 659 cerr << helpStr << endl;
Chris@144 660 exit(2);
Chris@144 661 }
Chris@144 662 set<string> writers = FeatureWriterFactory::getWriterTags();
Chris@144 663 bool first = true;
Chris@144 664 for (set<string>::const_iterator i = writers.begin();
Chris@144 665 i != writers.end(); ++i) {
Chris@144 666 if (!first) cout << " ";
Chris@144 667 cout << *i;
Chris@144 668 first = false;
Chris@144 669 }
Chris@144 670 cout << endl;
Chris@144 671 exit(0);
Chris@144 672 }
Chris@144 673 if (listFormats) {
Chris@144 674 if (!requestedWriterTags.empty() || skeletonFor != "") {
Chris@144 675 cerr << helpStr << endl;
Chris@144 676 exit(2);
Chris@144 677 }
Chris@144 678 QString extensions = AudioFileReaderFactory::getKnownExtensions();
Chris@144 679 QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
Chris@144 680 bool first = true;
Chris@144 681 foreach (QString s, extlist) {
Chris@144 682 if (!first) cout << " ";
Chris@144 683 s.replace("*.", "");
Chris@144 684 cout << s;
Chris@144 685 first = false;
Chris@144 686 }
Chris@144 687 cout << endl;
Chris@144 688 exit(0);
Chris@144 689 }
Chris@144 690 if (list) {
Chris@144 691 if (!requestedWriterTags.empty() || skeletonFor != "") {
Chris@144 692 cerr << helpStr << endl;
Chris@144 693 exit(2);
Chris@144 694 }
Chris@144 695 listTransforms();
Chris@144 696 exit(0);
Chris@144 697 }
Chris@0 698 if (skeletonFor != "") {
Chris@0 699 if (!requestedWriterTags.empty()) {
Chris@127 700 cerr << helpStr << endl;
Chris@0 701 exit(2);
Chris@0 702 }
Chris@0 703 printSkeleton(skeletonFor);
Chris@0 704 exit(0);
Chris@0 705 }
Chris@127 706 if (minVersion != "") {
Chris@127 707 if (!requestedWriterTags.empty()) {
Chris@127 708 cerr << helpStr << endl;
Chris@127 709 exit(2);
Chris@127 710 }
Chris@127 711 exit(checkMinVersion(myname, minVersion));
Chris@127 712 }
Chris@0 713
Chris@0 714 if (requestedTransformFiles.empty() &&
Chris@0 715 requestedTransformListFiles.empty() &&
Chris@0 716 requestedDefaultTransforms.empty()) {
Chris@127 717 cerr << myname
Chris@0 718 << ": no transform(s) specified" << endl;
Chris@127 719 cerr << helpStr << endl;
Chris@0 720 exit(2);
Chris@0 721 }
Chris@0 722
Chris@0 723 if (requestedWriterTags.empty()) {
Chris@127 724 cerr << myname
Chris@0 725 << ": no writer(s) specified" << endl;
Chris@127 726 cerr << helpStr << endl;
Chris@0 727 exit(2);
Chris@0 728 }
Chris@0 729
Chris@0 730 if (!boundaries.empty()) {
Chris@0 731 if (requestedSummaryTypes.empty()) {
Chris@127 732 cerr << myname
Chris@0 733 << ": summary segment boundaries provided, but no summary type specified"
Chris@0 734 << endl;
Chris@127 735 cerr << helpStr << endl;
Chris@0 736 exit(2);
Chris@0 737 }
Chris@0 738 }
Chris@0 739
Chris@20 740 QSettings settings;
Chris@20 741
Chris@0 742 #ifdef HAVE_FFTW3
Chris@0 743 settings.beginGroup("FFTWisdom");
Chris@0 744 QString wisdom = settings.value("wisdom").toString();
Chris@0 745 if (wisdom != "") {
Chris@0 746 fftw_import_wisdom_from_string(wisdom.toLocal8Bit().data());
Chris@0 747 }
Chris@0 748 settings.endGroup();
Chris@0 749 #endif
Chris@0 750
Chris@20 751 settings.beginGroup("RDF");
Chris@20 752 if (!settings.contains("rdf-indices")) {
Chris@20 753 QStringList list;
Chris@20 754 list << "http://www.vamp-plugins.org/rdf/plugins/index.txt";
Chris@20 755 settings.setValue("rdf-indices", list);
Chris@20 756 }
Chris@20 757 settings.endGroup();
Chris@20 758
Chris@0 759 FeatureExtractionManager manager;
Chris@0 760
Chris@116 761 manager.setNormalise(normalise);
Chris@116 762
Chris@0 763 if (!requestedSummaryTypes.empty()) {
Chris@0 764 if (!manager.setSummaryTypes(requestedSummaryTypes,
Chris@0 765 boundaries)) {
Chris@127 766 cerr << myname
Chris@0 767 << ": failed to set requested summary types" << endl;
Chris@0 768 exit(1);
Chris@0 769 }
Chris@0 770 }
Chris@102 771
Chris@102 772 manager.setSummariesOnly(summaryOnly);
Chris@116 773
Chris@0 774 vector<FeatureWriter *> writers;
Chris@0 775
Chris@0 776 for (set<string>::const_iterator i = requestedWriterTags.begin();
Chris@0 777 i != requestedWriterTags.end(); ++i) {
Chris@0 778
Chris@0 779 FeatureWriter *writer = FeatureWriterFactory::createWriter(*i);
Chris@0 780
Chris@0 781 if (!writer) {
Chris@127 782 cerr << myname << ": unknown feature writer \""
Chris@0 783 << *i << "\"" << endl;
Chris@127 784 cerr << helpStr << endl;
Chris@0 785 exit(2);
Chris@0 786 }
Chris@0 787
Chris@0 788 map<string, string> writerArgs;
Chris@0 789 FeatureWriter::ParameterList pl(writer->getSupportedParameters());
Chris@0 790
Chris@95 791 for (int k = 0; k < (int)pl.size(); ++k) {
Chris@0 792
Chris@0 793 string argbase = pl[k].name;
Chris@0 794 QString literal = QString("--%1-%2")
Chris@0 795 .arg(i->c_str()).arg(argbase.c_str());
Chris@0 796
Chris@95 797 for (int j = 0; j < (int)otherArgs.size(); ) {
Chris@0 798
Chris@0 799 if (otherArgs[j] != literal) {
Chris@0 800 ++j;
Chris@0 801 continue;
Chris@0 802 }
Chris@0 803
Chris@0 804 otherArgs.removeAt(j);
Chris@0 805
Chris@0 806 if (pl[k].hasArg) {
Chris@0 807 if (j < otherArgs.size()) {
Chris@0 808 writerArgs[argbase] = otherArgs[j].toStdString();
Chris@0 809 otherArgs.removeAt(j);
Chris@0 810 } else {
Chris@127 811 cerr << myname << ": "
Chris@0 812 << "argument required for \""
Chris@127 813 << literal << "\" option"
Chris@0 814 << endl;
Chris@127 815 cerr << helpStr << endl;
Chris@0 816 exit(2);
Chris@0 817 }
Chris@0 818 } else {
Chris@0 819 writerArgs[argbase] = "";
Chris@0 820 }
Chris@0 821 }
Chris@0 822 }
Chris@0 823
Chris@0 824 writer->setParameters(writerArgs);
Chris@0 825
Chris@0 826 writers.push_back(writer);
Chris@0 827 }
Chris@0 828
Chris@0 829 for (int i = 0; i < otherArgs.size(); ++i) {
Chris@0 830 if (otherArgs[i].startsWith("-")) {
Chris@127 831 cerr << myname << ": unknown option \""
Chris@127 832 << otherArgs[i] << "\"" << endl;
Chris@127 833 cerr << helpStr << endl;
Chris@0 834 exit(2);
Chris@0 835 }
Chris@0 836 }
Chris@0 837
Chris@0 838 if (otherArgs.empty()) {
Chris@127 839 cerr << myname << ": no input(s) specified" << endl;
Chris@127 840 cerr << helpStr << endl;
Chris@0 841 exit(2);
Chris@0 842 }
Chris@0 843
Chris@0 844 for (set<string>::const_iterator i = requestedTransformListFiles.begin();
Chris@0 845 i != requestedTransformListFiles.end(); ++i) {
Chris@0 846 PlaylistFileReader reader(i->c_str());
Chris@0 847 if (reader.isOK()) {
Chris@0 848 vector<QString> files = reader.load();
Chris@95 849 for (int j = 0; j < (int)files.size(); ++j) {
Chris@0 850 requestedTransformFiles.insert(files[j].toStdString());
Chris@0 851 }
Chris@0 852 } else {
Chris@127 853 cerr << myname << ": failed to read template list file \"" << *i << "\"" << endl;
Chris@0 854 exit(2);
Chris@0 855 }
Chris@0 856 }
Chris@0 857
Chris@0 858 QStringList sources;
Chris@0 859 if (!recursive) {
Chris@0 860 sources = otherArgs;
Chris@0 861 } else {
Chris@0 862 for (QStringList::const_iterator i = otherArgs.begin();
Chris@0 863 i != otherArgs.end(); ++i) {
Chris@0 864 if (QDir(*i).exists()) {
Chris@0 865 cerr << "Directory found and recursive flag set, scanning for audio files..." << endl;
Chris@0 866 int found = 0;
Chris@0 867 findSourcesRecursive(*i, sources, found);
Chris@0 868 cerr << "\rDone, found " << found << " supported audio file(s) " << endl;
Chris@0 869 } else {
Chris@0 870 sources.push_back(*i);
Chris@0 871 }
Chris@0 872 }
Chris@0 873 }
Chris@0 874
Chris@111 875 sources = expandPlaylists(sources);
Chris@117 876
Chris@28 877 bool good = true;
Chris@45 878 QSet<QString> badSources;
Chris@28 879
Chris@0 880 for (QStringList::const_iterator i = sources.begin();
Chris@0 881 i != sources.end(); ++i) {
Chris@0 882 try {
Chris@106 883 manager.addSource(*i, multiplex);
Chris@22 884 } catch (const std::exception &e) {
Chris@45 885 badSources.insert(*i);
Chris@21 886 cerr << "ERROR: Failed to process file \"" << i->toStdString()
Chris@21 887 << "\": " << e.what() << endl;
Chris@22 888 if (force) {
Chris@22 889 // print a note only if we have more files to process
Chris@22 890 QStringList::const_iterator j = i;
Chris@22 891 if (++j != sources.end()) {
Chris@22 892 cerr << "NOTE: \"--force\" option was provided, continuing (more errors may occur)" << endl;
Chris@22 893 }
Chris@22 894 } else {
Chris@45 895 cerr << "NOTE: If you want to continue with processing any further files after an" << endl
Chris@45 896 << "error like this, use the --force option" << endl;
Chris@28 897 good = false;
Chris@22 898 break;
Chris@22 899 }
Chris@0 900 }
Chris@0 901 }
Chris@45 902
Chris@45 903 if (good) {
Chris@45 904
Chris@45 905 bool haveFeatureExtractor = false;
Chris@45 906
Chris@45 907 for (set<string>::const_iterator i = requestedTransformFiles.begin();
Chris@45 908 i != requestedTransformFiles.end(); ++i) {
Chris@45 909 if (manager.addFeatureExtractorFromFile(i->c_str(), writers)) {
Chris@45 910 haveFeatureExtractor = true;
Chris@120 911 } else {
Chris@120 912 cerr << "ERROR: Failed to add feature extractor from transform file \"" << *i << "\"" << endl;
Chris@120 913 good = false;
Chris@45 914 }
Chris@45 915 }
Chris@45 916
Chris@45 917 for (set<string>::const_iterator i = requestedDefaultTransforms.begin();
Chris@45 918 i != requestedDefaultTransforms.end(); ++i) {
Chris@45 919 if (manager.addDefaultFeatureExtractor(i->c_str(), writers)) {
Chris@45 920 haveFeatureExtractor = true;
Chris@120 921 } else {
Chris@120 922 cerr << "ERROR: Failed to add default feature extractor for transform \"" << *i << "\"" << endl;
Chris@120 923 good = false;
Chris@45 924 }
Chris@45 925 }
Chris@45 926
Chris@45 927 if (!haveFeatureExtractor) {
Chris@127 928 cerr << myname << ": no feature extractors added" << endl;
Chris@45 929 good = false;
Chris@45 930 }
Chris@45 931 }
Chris@45 932
Chris@45 933 if (good) {
Chris@106 934 QStringList goodSources;
Chris@106 935 foreach (QString source, sources) {
Chris@106 936 if (!badSources.contains(source)) {
Chris@106 937 goodSources.push_back(source);
Chris@106 938 }
Chris@106 939 }
Chris@106 940 if (multiplex) {
Chris@45 941 try {
Chris@106 942 manager.extractFeaturesMultiplexed(goodSources);
Chris@45 943 } catch (const std::exception &e) {
Chris@106 944 cerr << "ERROR: Feature extraction failed: "
Chris@106 945 << e.what() << endl;
Chris@106 946 }
Chris@106 947 } else {
Chris@106 948 for (QStringList::const_iterator i = goodSources.begin();
Chris@106 949 i != goodSources.end(); ++i) {
Chris@106 950 std::cerr << "Extracting features for: \"" << i->toStdString()
Chris@106 951 << "\"" << std::endl;
Chris@106 952 try {
Chris@112 953 manager.extractFeatures(*i);
Chris@106 954 } catch (const std::exception &e) {
Chris@106 955 cerr << "ERROR: Feature extraction failed for \""
Chris@106 956 << i->toStdString() << "\": " << e.what() << endl;
Chris@106 957 if (force) {
Chris@106 958 // print a note only if we have more files to process
Chris@106 959 QStringList::const_iterator j = i;
Chris@106 960 if (++j != sources.end()) {
Chris@106 961 cerr << "NOTE: \"--force\" option was provided, continuing (more errors may occur)" << endl;
Chris@106 962 }
Chris@106 963 } else {
Chris@106 964 cerr << "NOTE: If you want to continue with processing any further files after an" << endl
Chris@106 965 << "error like this, use the --force option" << endl;
Chris@106 966 good = false;
Chris@106 967 break;
Chris@45 968 }
Chris@45 969 }
Chris@45 970 }
Chris@45 971 }
Chris@45 972 }
Chris@0 973
Chris@95 974 for (int i = 0; i < (int)writers.size(); ++i) delete writers[i];
Chris@0 975
Chris@0 976 #ifdef HAVE_FFTW3
Chris@0 977 settings.beginGroup("FFTWisdom");
Chris@0 978 char *cwisdom = fftw_export_wisdom_to_string();
Chris@0 979 if (cwisdom) {
Chris@0 980 settings.setValue("wisdom", cwisdom);
Chris@0 981 fftw_free(cwisdom);
Chris@0 982 }
Chris@0 983 settings.endGroup();
Chris@0 984 #endif
Chris@0 985
Chris@0 986 TempDirectory::getInstance()->cleanup();
Chris@0 987
Chris@28 988 if (good) return 0;
Chris@28 989 else return 1;
Chris@0 990 }
Chris@0 991
Chris@0 992