annotate runner/main.cpp @ 394:885960538dd4

Build a static-ish binary
author Chris Cannam
date Tue, 09 Jun 2020 16:59:44 +0100
parents f33924771c5f
children
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@263 39 #include "base/Debug.h"
Chris@0 40
Chris@0 41 #include "data/fileio/AudioFileReaderFactory.h"
Chris@0 42 #include "data/fileio/PlaylistFileReader.h"
Chris@0 43
Chris@0 44 #include "transform/Transform.h"
Chris@0 45 #include "transform/TransformFactory.h"
Chris@0 46
Chris@0 47 #include "FeatureExtractionManager.h"
Chris@0 48 #include "transform/FeatureWriter.h"
Chris@0 49 #include "FeatureWriterFactory.h"
Chris@0 50
Chris@0 51 #include "rdf/RDFTransformFactory.h"
Chris@0 52
Chris@0 53 #include <vamp-hostsdk/PluginSummarisingAdapter.h>
Chris@0 54
Chris@0 55 #ifdef HAVE_FFTW3
Chris@0 56 #include <fftw3.h>
Chris@0 57 #endif
Chris@0 58
Chris@0 59 // Desired options:
Chris@0 60 //
Chris@0 61 // * output preference:
Chris@0 62 // - all data in one file
Chris@0 63 // - one file per input file
Chris@0 64 // - one file per input file per transform
Chris@0 65 // - (any use for: one file per transform?)
Chris@0 66 //
Chris@0 67 // * output location:
Chris@0 68 // - same directory as input file
Chris@0 69 // - current directory
Chris@0 70 //
Chris@0 71 // * output filename:
Chris@0 72 // - based on input (obvious choice for one file per input file modes)
Chris@0 73 // - specified on command line (obvious choice for all in one file mode)
Chris@0 74 //
Chris@0 75 // * output format: one or more of
Chris@0 76 // - RDF
Chris@0 77 // - AudioDB
Chris@0 78 // - Vamp Simple Host format
Chris@0 79 // - CSV
Chris@0 80 //
Chris@0 81 // * input handling:
Chris@0 82 // - run each transform on each input file separately
Chris@0 83 // - provide all input files to the same transform, one per channel
Chris@0 84 //
Chris@0 85 // * format-specific options:
Chris@0 86 // - RDF format: fancy/plain RDF
Chris@0 87 // - CSV format: separator, timestamp type
Chris@0 88 // note: do the output file/location also count as format-specific options?
Chris@0 89 // an output writer that wrote to a database would have different options...
Chris@0 90 //
Chris@0 91 // * debug level and progress output
Chris@0 92 //
Chris@0 93 // * other potential options:
Chris@0 94 // - ignore version mismatches in Transform specifications
Chris@0 95 // - sample rate: force a given rate; use file rate instead of rate in
Chris@0 96 // Transform spec
Chris@0 97 //
Chris@0 98 // * other potential instructions:
Chris@0 99 // - write out a skeleton Transform file for a specified plugin
Chris@0 100 // - write out skeleton RDF for a plugin library (i.e. do the job of
Chris@0 101 // RDF template_generator)
Chris@0 102 // - verify that RDF for a plugin library matches the plugin
Chris@0 103 //
Chris@0 104 // MAYBE:
Chris@0 105 // * transform(s) to run:
Chris@0 106 // - supply transform file names on command line
Chris@0 107 // - use all transforms found in a given directory?
Chris@0 108 //
Chris@0 109 // MAYBE:
Chris@0 110 // * input files to transform:
Chris@0 111 // - supply file names or URIs on command line
Chris@0 112 // - use all files in a given directory or tree
Chris@0 113
Chris@0 114 static QString
Chris@0 115 wrap(QString s, int len, int pfx = 0)
Chris@0 116 {
Chris@0 117 QString ws;
Chris@0 118 QStringList sl(s.split(' '));
Chris@0 119 int i = 0, c = 0;
Chris@0 120 while (i < sl.size()) {
Chris@0 121 int wl = sl[i].length();
Chris@0 122 if (c + wl < len) {
Chris@0 123 if (c > 0) {
Chris@0 124 ws += ' ';
Chris@0 125 ++c;
Chris@0 126 }
Chris@0 127 } else {
Chris@0 128 if (c > 0) {
Chris@0 129 ws += '\n';
Chris@0 130 for (int j = 0; j < pfx; ++j) ws += ' ';
Chris@0 131 c = 0;
Chris@0 132 }
Chris@0 133 }
Chris@0 134 ws += sl[i];
Chris@0 135 c += wl;
Chris@0 136 ++i;
Chris@0 137 }
Chris@0 138 return ws;
Chris@0 139 }
Chris@0 140
Chris@240 141 static QString wrapCol(QString s) {
Chris@240 142 return wrap(s, 56, 22);
Chris@240 143 }
Chris@240 144
Chris@127 145 static bool
Chris@127 146 isVersionNewerThan(QString a, QString b) // from VersionTester in svapp
Chris@127 147 {
Chris@127 148 QRegExp re("[._-]");
Chris@127 149 QStringList alist = a.split(re, QString::SkipEmptyParts);
Chris@127 150 QStringList blist = b.split(re, QString::SkipEmptyParts);
Chris@127 151 int ae = alist.size();
Chris@127 152 int be = blist.size();
Chris@127 153 int e = std::max(ae, be);
Chris@127 154 for (int i = 0; i < e; ++i) {
Chris@127 155 int an = 0, bn = 0;
Chris@127 156 if (i < ae) {
Chris@127 157 an = alist[i].toInt();
Chris@127 158 if (an == 0 && alist[i] != "0") {
Chris@127 159 an = -1; // non-numeric field -> "-pre1" etc
Chris@127 160 }
Chris@127 161 }
Chris@127 162 if (i < be) {
Chris@127 163 bn = blist[i].toInt();
Chris@127 164 if (bn == 0 && blist[i] != "0") {
Chris@127 165 bn = -1;
Chris@127 166 }
Chris@127 167 }
Chris@127 168 if (an < bn) return false;
Chris@127 169 if (an > bn) return true;
Chris@127 170 }
Chris@127 171 return false;
Chris@127 172 }
Chris@127 173
Chris@127 174 static int
Chris@127 175 checkMinVersion(QString myname, QString v)
Chris@127 176 {
Chris@127 177 if (v == RUNNER_VERSION) {
Chris@127 178 return 0;
Chris@127 179 } else if (isVersionNewerThan(RUNNER_VERSION, v)) {
Chris@127 180 return 0;
Chris@127 181 } else {
Chris@127 182 cerr << myname << ": version "
Chris@127 183 << RUNNER_VERSION << " is less than requested min version "
Chris@127 184 << v << ", failing" << endl;
Chris@127 185 return 1;
Chris@127 186 }
Chris@127 187 }
Chris@127 188
Chris@125 189 void printUsage(QString myname)
Chris@0 190 {
Chris@0 191 cerr << endl;
Chris@2 192 cerr << "Sonic Annotator v" << RUNNER_VERSION << endl;
Chris@0 193 cerr << "A utility for batch feature extraction from audio files." << endl;
Chris@240 194 cerr << "Mark Levy, Chris Sutton, and Chris Cannam, Queen Mary, University of London." << endl;
Chris@369 195 cerr << "Copyright 2007-2020 Queen Mary, University of London." << endl;
Chris@0 196 cerr << endl;
Chris@0 197 cerr << "This program is free software. You may redistribute copies of it under the" << endl;
Chris@0 198 cerr << "terms of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>." << endl;
Chris@0 199 cerr << "This program is supplied with NO WARRANTY, to the extent permitted by law." << endl;
Chris@0 200 cerr << endl;
Chris@125 201 cerr << "Usage: " << endl;
Chris@127 202 cerr << " " << myname
Chris@125 203 << " [-mrnf] -t transform.ttl [..] -w <writer> [..] <audio> [..]" << endl;
Chris@127 204 cerr << " " << myname
Chris@125 205 << " [-mrnf] -T translist.txt [..] -w <writer> [..] <audio> [..]" << endl;
Chris@127 206 cerr << " " << myname
Chris@240 207 << " [-mrnf] -d <id> [..] -w <writer> [..] <audio> [...]" << endl;
Chris@127 208 cerr << " " << myname
Chris@0 209 << " -s <transform>" << endl;
Chris@127 210 cerr << " " << myname
Chris@46 211 << " [-lhv]" << endl;
Chris@0 212 cerr << endl;
Chris@0 213 cerr << "Where <audio> is an audio file or URL to use as input: either a local file" << endl;
Chris@125 214 cerr << "path, local \"file://\" URL, or remote \"http://\" or \"ftp://\" URL;" << endl;
Chris@240 215 cerr << "and <id> is a transform id of the form vamp:libname:plugin:output." << endl;
Chris@0 216 cerr << endl;
Chris@125 217 }
Chris@125 218
Chris@197 219 void printOptionHelp(std::string writer, FeatureWriter::Parameter &p)
Chris@197 220 {
Chris@197 221 cerr << " --" << writer << "-" << p.name << " ";
Chris@197 222 int spaceage = 16 - int(writer.length()) - int(p.name.length());
Chris@197 223 if (p.hasArg) { cerr << "<X> "; spaceage -= 4; }
Chris@197 224 for (int k = 0; k < spaceage; ++k) cerr << " ";
Chris@197 225 QString s(p.description.c_str());
Chris@240 226 s = wrapCol(s);
Chris@197 227 cerr << s << endl;
Chris@197 228 }
Chris@197 229
Chris@142 230 void printHelp(QString myname, QString w)
Chris@125 231 {
Chris@142 232 std::string writer = w.toStdString();
Chris@142 233
Chris@125 234 printUsage(myname);
Chris@0 235
Chris@0 236 QString extensions = AudioFileReaderFactory::getKnownExtensions();
Chris@0 237 QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
Chris@0 238 if (!extlist.empty()) {
Chris@0 239 cerr << "The following audio file extensions are recognised:" << endl;
Chris@0 240 cerr << " ";
Chris@0 241 int c = 2;
Chris@0 242 for (int i = 0; i < extlist.size(); ++i) {
Chris@0 243 QString ext = extlist[i];
Chris@0 244 if (ext.startsWith("*.")) ext = ext.right(ext.length()-2);
Chris@0 245 c += ext.length() + 2;
Chris@0 246 if (c >= 80) {
Chris@0 247 cerr << "\n ";
Chris@0 248 c -= 78;
Chris@0 249 }
Chris@127 250 cerr << ext;
Chris@0 251 if (i + 1 == extlist.size()) cerr << ".";
Chris@0 252 else cerr << ", ";
Chris@0 253 }
Chris@0 254 cerr << endl;
Chris@0 255 }
Chris@0 256
Chris@0 257 cerr << "Playlist files in M3U format are also supported." << endl;
Chris@0 258 cerr << endl;
Chris@125 259
Chris@370 260 if (extlist.contains("*.mp3")) {
Chris@370 261 QString warning = "(Note: It's wise to avoid using %1 as a source format, even in cases where lossy compression is not considered problematic: the handling of initial encoder delay can vary between decoders, and possibly even between builds of %2, so feature timings may not be consistent.)";
Chris@370 262 if (extlist.contains("*.m4a")) {
Chris@370 263 warning = warning.arg("mp3 or mp4 (aac, m4a)").arg(myname);
Chris@370 264 } else {
Chris@370 265 warning = warning.arg("mp3").arg(myname);
Chris@370 266 }
Chris@370 267 cerr << wrap(warning, 78, 0) << endl << endl;
Chris@370 268 }
Chris@370 269
Chris@125 270 set<string> writers = FeatureWriterFactory::getWriterTags();
Chris@142 271
Chris@142 272 QString writerText = "Supported writer types are: ";
Chris@0 273 for (set<string>::const_iterator i = writers.begin();
Chris@0 274 i != writers.end(); ) {
Chris@142 275 writerText += i->c_str();
Chris@142 276 if (++i != writers.end()) writerText += ", ";
Chris@142 277 else writerText += ".";
Chris@0 278 }
Chris@240 279 writerText = wrapCol(writerText);
Chris@0 280
Chris@142 281 if (writer == "" || writers.find(writer) == writers.end()) {
Chris@0 282
Chris@142 283 cerr << "Transformation options:" << endl;
Chris@142 284 cerr << endl;
Chris@240 285 cerr << " -t, --transform <T> "
Chris@240 286 << wrapCol("Apply transform described in transform file <T> to"
Chris@240 287 " all input audio files. You may supply this option"
Chris@240 288 " multiple times. You must supply this option, -T, or -d"
Chris@240 289 " at least once for any work to be done. Transform format"
Chris@240 290 " may be SV transform XML or Vamp transform RDF/Turtle."
Chris@240 291 " A skeleton transform file for"
Chris@240 292 " a given transform id can be generated using the"
Chris@240 293 " -s option (see below). See accompanying"
Chris@240 294 " documentation for transform examples.")
Chris@240 295 << endl << endl;
Chris@240 296 cerr << " -T, --transforms <T> "
Chris@240 297 << wrapCol("Apply all transforms described in transform files"
Chris@240 298 " whose names are listed in text file <T>. You may supply"
Chris@240 299 " this option multiple times.")
Chris@240 300 << endl << endl;
Chris@240 301 cerr << " -d, --default <I> "
Chris@240 302 << wrapCol("Apply the default transform for transform id <I>. This"
Chris@240 303 " is equivalent to generating a skeleton transform for the"
Chris@240 304 " id (using the -s option, below) and then applying that,"
Chris@240 305 " unmodified, with the -t option in the normal way. Note"
Chris@240 306 " that results may vary, as default"
Chris@240 307 " processing parameters may change between releases of "
Chris@240 308 + myname + " as well as of individual plugins. Do not use"
Chris@240 309 " this in production systems. You may supply this option"
Chris@240 310 " multiple times, and mix it with -t and -T.")
Chris@240 311 << endl << endl;
Chris@240 312 cerr << " -w, --writer <W> Write output using writer type <W>.\n"
Chris@240 313 << " " << writerText << endl
Chris@240 314 << " "
Chris@240 315 << wrapCol("You may supply this option multiple times. You must"
Chris@240 316 " supply this option at least once for any work to be done.")
Chris@240 317 << endl << endl;
Chris@240 318 cerr << " -S, --summary <S> "
Chris@240 319 << wrapCol("In addition to the result features, write summary feature"
Chris@240 320 " of summary type <S>.") << endl
Chris@240 321 << " "
Chris@240 322 << wrapCol("Supported summary types are min, max, mean, median, mode,"
Chris@240 323 " sum, variance, sd, count.") << endl
Chris@240 324 << " You may supply this option multiple times."
Chris@240 325 << endl << endl;
Chris@240 326 cerr << " --summary-only "
Chris@240 327 << wrapCol("Write only summary features; do not write the regular"
Chris@240 328 " result features.")
Chris@240 329 << endl << endl;
Chris@240 330 cerr << " --segments <A>,<B>[,...]\n "
Chris@240 331 << wrapCol("Summarise in segments, with segment boundaries"
Chris@240 332 " at A, B, ... seconds.")
Chris@240 333 << endl << endl;
Chris@240 334 cerr << " --segments-from <F>\n "
Chris@240 335 << wrapCol("Summarise in segments, with segment boundaries"
Chris@240 336 " at times read from the text file <F>. (one time per"
Chris@240 337 " line, in seconds).")
Chris@240 338 << endl << endl;
Chris@240 339 cerr << " -m, --multiplex "
Chris@240 340 << wrapCol("If multiple input audio files are given, use mono"
Chris@240 341 " mixdowns of the files as the input channels for a single"
Chris@240 342 " invocation of each transform, instead of running the"
Chris@240 343 " transform against all files separately. The first file"
Chris@240 344 " will be used for output reference name and sample rate.")
Chris@240 345 << endl << endl;
Chris@240 346 cerr << " -r, --recursive "
Chris@240 347 << wrapCol("If any of the <audio> arguments is found to be a local"
Chris@240 348 " directory, search the tree starting at that directory"
Chris@240 349 " for all supported audio files and take all of those as"
Chris@240 350 " input in place of it.")
Chris@240 351 << endl << endl;
Chris@240 352 cerr << " -n, --normalise "
Chris@240 353 << wrapCol("Normalise each input audio file to signal abs max = 1.f.")
Chris@240 354 << endl << endl;
Chris@240 355 cerr << " -f, --force "
Chris@240 356 << wrapCol("Continue with subsequent files following an error.")
Chris@240 357 << endl << endl;
Chris@263 358 cerr << " -q, --quiet "
Chris@263 359 << wrapCol("Suppress informational output that would otherwise be printed to stderr and to a log file. Sonic Annotator may run faster with this option, especially if the application data directory is on a shared storage resource, but no diagnostic information will be available except for the application's return code.")
Chris@263 360 << endl << endl;
Chris@240 361 cerr << "Housekeeping options:"
Chris@240 362 << endl << endl;
Chris@144 363 cerr << " -l, --list List available transform ids to standard output." << endl;
Chris@144 364 cerr << " --list-writers List supported writer types to standard output." << endl;
Chris@144 365 cerr << " --list-formats List supported input audio formats to standard output." << endl;
Chris@142 366 cerr << endl;
Chris@240 367 cerr << " -s, --skeleton <I> "
Chris@240 368 << wrapCol("Generate a skeleton RDF transform file for transform id"
Chris@240 369 " <I>, with default parameters for that transform, and write it"
Chris@240 370 " to standard output.")
Chris@240 371 << endl << endl;
Chris@142 372 cerr << " -v, --version Show the version number and exit." << endl;
Chris@142 373 cerr << endl;
Chris@240 374 cerr << " --minversion <V> "
Chris@240 375 << wrapCol("Exit with successful return code if the version of "
Chris@240 376 + myname + " is at least <V>, failure otherwise."
Chris@240 377 " For scripts that depend on certain option support.")
Chris@240 378 << endl << endl;
Chris@255 379 cerr << " --transform-minversion <I> <V>\n"
Chris@255 380 << " "
Chris@255 381 << wrapCol("Exit with successful return code if the plugin providing "
Chris@255 382 " transform id <I> is at least <V>, failure otherwise.")
Chris@255 383 << endl << endl;
Chris@142 384 cerr << " -h, --help Show help." << endl;
Chris@142 385 cerr << " -h, --help <W> Show help for writer type W." << endl;
Chris@142 386 cerr << " " << writerText << endl;
Chris@142 387
Chris@240 388 cerr << endl
Chris@240 389 << wrap("If no -w (or --writer) options are supplied, one of the"
Chris@240 390 " housekeeping options (-l -s -v -h or long equivalent) must"
Chris@240 391 " be given instead.", 78, 0)
Chris@240 392 << endl;
Chris@142 393
Chris@142 394 } else {
Chris@142 395
Chris@142 396 FeatureWriter *w = FeatureWriterFactory::createWriter(writer);
Chris@0 397 if (!w) {
Chris@142 398 cerr << " (Internal error: failed to create writer of known type \""
Chris@142 399 << writer << "\")" << endl;
Chris@142 400 return;
Chris@0 401 }
Chris@144 402 cerr << "Feature writer \"" << writer << "\":" << endl << endl;
Chris@144 403 cerr << " " << wrap(w->getDescription().c_str(), 76, 2) << endl << endl;
Chris@0 404 FeatureWriter::ParameterList params = w->getSupportedParameters();
Chris@0 405 delete w;
Chris@0 406 if (params.empty()) {
Chris@144 407 cerr << " No additional options are available for this writer." << endl << endl;
Chris@142 408 return;
Chris@0 409 }
Chris@197 410 FeatureWriter::ParameterList mandatory;
Chris@197 411 bool haveOptional = false;
Chris@197 412 for (auto &p: params) {
Chris@197 413 if (p.mandatory) mandatory.push_back(p);
Chris@197 414 else haveOptional = true;
Chris@197 415 }
Chris@197 416 if (!mandatory.empty()) {
Chris@197 417 cerr << "Mandatory parameters for writer type \"" << writer << "\":" << endl;
Chris@197 418 cerr << endl;
Chris@197 419 for (auto &p: mandatory) {
Chris@197 420 printOptionHelp(writer, p);
Chris@197 421 }
Chris@197 422 cerr << endl;
Chris@197 423 }
Chris@197 424 if (haveOptional) {
Chris@197 425 cerr << "Additional options for writer type \"" << writer << "\":" << endl;
Chris@197 426 cerr << endl;
Chris@197 427 for (auto &p: params) {
Chris@197 428 if (p.mandatory) continue;
Chris@197 429 printOptionHelp(writer, p);
Chris@197 430 }
Chris@0 431 }
Chris@0 432 }
Chris@0 433
Chris@0 434 cerr << endl;
Chris@0 435 }
Chris@0 436
Chris@0 437 void
Chris@0 438 listTransforms()
Chris@0 439 {
Chris@0 440 TransformList transforms =
Chris@0 441 TransformFactory::getInstance()->getAllTransformDescriptions();
Chris@0 442
Chris@392 443 set<QString> ids;
Chris@392 444 for (auto t: transforms) {
Chris@392 445 if (t.type == TransformDescription::Analysis) {
Chris@392 446 ids.insert(t.identifier);
Chris@0 447 }
Chris@0 448 }
Chris@392 449
Chris@392 450 for (auto id: ids) {
Chris@392 451 cout << id << endl;
Chris@392 452 }
Chris@0 453 }
Chris@0 454
Chris@0 455 void
Chris@0 456 printSkeleton(QString id)
Chris@0 457 {
Chris@0 458 Transform transform =
Chris@0 459 TransformFactory::getInstance()->getDefaultTransformFor(id);
Chris@0 460 cout << "@prefix xsd: <http://www.w3.org/2001/XMLSchema#> ." << endl
Chris@0 461 << "@prefix vamp: <http://purl.org/ontology/vamp/> ." << endl
Chris@0 462 << "@prefix : <#> ." << endl << endl;
Chris@0 463 QString rdf = RDFTransformFactory::writeTransformToRDF
Chris@0 464 (transform, ":transform");
Chris@127 465 cout << rdf;
Chris@0 466 }
Chris@0 467
Chris@255 468 static int
Chris@255 469 checkTransformMinVersion(QString myname, QString id, int version)
Chris@255 470 {
Chris@255 471 Transform transform =
Chris@255 472 TransformFactory::getInstance()->getDefaultTransformFor(id);
Chris@255 473 QString pvs = transform.getPluginVersion();
Chris@255 474 bool ok = false;
Chris@255 475 int pv = pvs.toInt(&ok);
Chris@255 476 if (!ok) {
Chris@255 477 cerr << myname << ": transform version \"" << pvs << "\" is not an integer"
Chris@255 478 << endl;
Chris@255 479 return 1;
Chris@255 480 }
Chris@255 481 if (pv >= version) {
Chris@255 482 return 0;
Chris@255 483 } else {
Chris@255 484 return 1;
Chris@255 485 }
Chris@255 486 }
Chris@255 487
Chris@0 488 void
Chris@0 489 findSourcesRecursive(QString dirname, QStringList &addTo, int &found)
Chris@0 490 {
Chris@0 491 QDir dir(dirname);
Chris@0 492
Chris@0 493 QString printable = dir.dirName().left(20);
Chris@265 494 SVCERR << "\rScanning \"" << printable << "\"..."
Chris@127 495 << QString(" ").left(20 - printable.length())
Chris@0 496 << " [" << found << " audio file(s)]";
Chris@0 497
Chris@0 498 QString extensions = AudioFileReaderFactory::getKnownExtensions();
Chris@0 499 QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
Chris@0 500
Chris@0 501 QStringList files = dir.entryList
Chris@0 502 (extlist, QDir::Files | QDir::Readable);
Chris@0 503 for (int i = 0; i < files.size(); ++i) {
Chris@0 504 addTo.push_back(dir.filePath(files[i]));
Chris@0 505 ++found;
Chris@0 506 }
Chris@0 507
Chris@0 508 QStringList subdirs = dir.entryList
Chris@0 509 (QStringList(), QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
Chris@0 510 for (int i = 0; i < subdirs.size(); ++i) {
Chris@0 511 findSourcesRecursive(dir.filePath(subdirs[i]), addTo, found);
Chris@0 512 }
Chris@0 513 }
Chris@0 514
Chris@111 515 QStringList
Chris@111 516 expandPlaylists(QStringList sources)
Chris@111 517 {
Chris@111 518 QStringList expanded;
Chris@111 519 foreach (QString path, sources) {
Chris@111 520 if (QFileInfo(path).suffix().toLower() == "m3u") {
Chris@265 521 SVDEBUG << "Expanding m3u playlist file \"" << path << "\"" << endl;
Chris@111 522 ProgressPrinter retrievalProgress("Opening playlist file...");
Chris@111 523 FileSource source(path, &retrievalProgress);
Chris@111 524 if (!source.isAvailable()) {
Chris@117 525 // Don't fail or throw an exception here, just keep
Chris@117 526 // the file in the list -- it will be tested again
Chris@117 527 // when adding it as a source and that's the proper
Chris@117 528 // time to fail. All we're concluding here is that it
Chris@117 529 // isn't a valid playlist
Chris@117 530 expanded.push_back(path);
Chris@117 531 continue;
Chris@111 532 }
Chris@111 533 source.waitForData();
Chris@111 534 PlaylistFileReader reader(source);
Chris@111 535 if (reader.isOK()) {
Chris@111 536 vector<QString> files = reader.load();
Chris@111 537 for (int i = 0; i < (int)files.size(); ++i) {
Chris@111 538 expanded.push_back(files[i]);
Chris@111 539 }
Chris@265 540 SVDEBUG << "Done, m3u playlist references "
Chris@265 541 << files.size() << " file(s)" << endl;
Chris@111 542 }
Chris@111 543 } else {
Chris@111 544 // not a playlist
Chris@111 545 expanded.push_back(path);
Chris@111 546 }
Chris@111 547 }
Chris@111 548 return expanded;
Chris@111 549 }
Chris@0 550
Chris@182 551 bool
Chris@182 552 readSegmentBoundaries(QString url,
Chris@182 553 Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries &boundaries)
Chris@182 554 {
Chris@182 555 FileSource source(url);
Chris@182 556 if (!source.isAvailable()) {
Chris@265 557 SVCERR << "File or URL \"" << url << "\" could not be retrieved" << endl;
Chris@182 558 return false;
Chris@182 559 }
Chris@182 560 source.waitForData();
Chris@182 561
Chris@182 562 QString filename = source.getLocalFilename();
Chris@182 563 QFile file(filename);
Chris@182 564 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
Chris@265 565 SVCERR << "File \"" << filename << "\" could not be read" << endl;
Chris@182 566 return false;
Chris@182 567 }
Chris@182 568
Chris@182 569 QTextStream in(&file);
Chris@182 570 int lineNo = 0;
Chris@182 571
Chris@182 572 while (!in.atEnd()) {
Chris@182 573
Chris@182 574 ++lineNo;
Chris@182 575
Chris@182 576 QString line = in.readLine();
Chris@182 577 if (line.startsWith("#")) continue;
Chris@182 578
Chris@182 579 QStringList bits = line.split(",", QString::SkipEmptyParts);
Chris@182 580 QString importantBit;
Chris@182 581 if (!bits.empty()) {
Chris@182 582 bits = bits[0].split(" ", QString::SkipEmptyParts);
Chris@182 583 }
Chris@182 584 if (!bits.empty()) {
Chris@182 585 importantBit = bits[0];
Chris@182 586 }
Chris@182 587 if (importantBit == QString()) {
Chris@265 588 SVCERR << "WARNING: Skipping line " << lineNo << " (no content found)"
Chris@182 589 << endl;
Chris@182 590 continue;
Chris@182 591 }
Chris@182 592 bool good = false;
Chris@182 593 boundaries.insert(Vamp::RealTime::fromSeconds
Chris@182 594 (importantBit.toDouble(&good)));
Chris@182 595 if (!good) {
Chris@265 596 SVCERR << "Unparseable or non-numeric segment boundary at line "
Chris@182 597 << lineNo << endl;
Chris@182 598 return false;
Chris@182 599 }
Chris@182 600 }
Chris@182 601
Chris@182 602 return true;
Chris@182 603 }
Chris@182 604
Chris@0 605 int main(int argc, char **argv)
Chris@0 606 {
Chris@0 607 QCoreApplication application(argc, argv);
Chris@0 608
Chris@0 609 QCoreApplication::setOrganizationName("QMUL");
Chris@0 610 QCoreApplication::setOrganizationDomain("qmul.ac.uk");
Chris@0 611 QCoreApplication::setApplicationName("Sonic Annotator");
Chris@0 612
Chris@0 613 QStringList args = application.arguments();
Chris@0 614 set<string> requestedWriterTags;
Chris@0 615 set<string> requestedTransformFiles;
Chris@0 616 set<string> requestedTransformListFiles;
Chris@0 617 set<string> requestedDefaultTransforms;
Chris@0 618 set<string> requestedSummaryTypes;
Chris@21 619 bool force = false;
Chris@106 620 bool multiplex = false;
Chris@0 621 bool recursive = false;
Chris@116 622 bool normalise = false;
Chris@263 623 bool quiet = false;
Chris@0 624 bool list = false;
Chris@144 625 bool listWriters = false;
Chris@144 626 bool listFormats = false;
Chris@0 627 bool summaryOnly = false;
Chris@0 628 QString skeletonFor = "";
Chris@127 629 QString minVersion = "";
Chris@255 630 pair<QString, QString> transformMinVersion;
Chris@0 631 QString myname = args[0];
Chris@0 632 myname = QFileInfo(myname).baseName();
Chris@0 633 QStringList otherArgs;
Chris@0 634 Vamp::HostExt::PluginSummarisingAdapter::SegmentBoundaries boundaries;
Chris@0 635
Chris@0 636 QString helpStr = myname + ": use -h or --help option for help";
Chris@0 637
Chris@0 638 for (int i = 1; i < args.size(); ++i) {
Chris@0 639
Chris@0 640 QString arg = args[i];
Chris@0 641 bool last = ((i + 1) == args.size());
Chris@0 642
Chris@0 643 if (arg == "-h" || arg == "--help" || arg == "-?") {
Chris@142 644 QString writer;
Chris@142 645 if (!last) {
Chris@142 646 writer = args[i+1];
Chris@142 647 }
Chris@142 648 printHelp(myname, writer);
Chris@125 649 return 0;
Chris@0 650 }
Chris@0 651
Chris@46 652 if (arg == "-v" || arg == "--version") {
Chris@46 653 std::cout << RUNNER_VERSION << std::endl;
Chris@46 654 return 0;
Chris@46 655 }
Chris@46 656
Chris@0 657 if (arg == "-w" || arg == "--writer") {
Chris@0 658 if (last || args[i+1].startsWith("-")) {
Chris@127 659 cerr << myname << ": argument expected for \""
Chris@127 660 << arg << "\" option" << endl;
Chris@127 661 cerr << helpStr << endl;
Chris@0 662 exit(2);
Chris@0 663 } else {
Chris@0 664 string tag = args[++i].toStdString();
Chris@0 665 if (requestedWriterTags.find(tag) != requestedWriterTags.end()) {
Chris@127 666 cerr << myname << ": NOTE: duplicate specification of writer type \"" << tag << "\" ignored" << endl;
Chris@0 667 } else {
Chris@0 668 requestedWriterTags.insert(tag);
Chris@0 669 }
Chris@0 670 continue;
Chris@0 671 }
Chris@0 672 } else if (arg == "-t" || arg == "--transform") {
Chris@0 673 if (last || args[i+1].startsWith("-")) {
Chris@127 674 cerr << myname << ": argument expected for \""
Chris@127 675 << arg << "\" option" << endl;
Chris@127 676 cerr << helpStr << endl;
Chris@0 677 exit(2);
Chris@0 678 } else {
Chris@0 679 string transform = args[++i].toStdString();
Chris@0 680 if (requestedTransformFiles.find(transform) !=
Chris@0 681 requestedTransformFiles.end()) {
Chris@127 682 cerr << myname << ": NOTE: duplicate specification of transform file \"" << transform << "\" ignored" << endl;
Chris@0 683 } else {
Chris@0 684 requestedTransformFiles.insert(transform);
Chris@0 685 }
Chris@0 686 continue;
Chris@0 687 }
Chris@0 688 } else if (arg == "-T" || arg == "--transforms") {
Chris@0 689 if (last || args[i+1].startsWith("-")) {
Chris@127 690 cerr << myname << ": argument expected for \""
Chris@127 691 << arg << "\" option" << endl;
Chris@127 692 cerr << helpStr << endl;
Chris@0 693 exit(2);
Chris@0 694 } else {
Chris@0 695 string transform = args[++i].toStdString();
Chris@0 696 if (requestedTransformListFiles.find(transform) !=
Chris@0 697 requestedTransformListFiles.end()) {
Chris@127 698 cerr << myname << ": NOTE: duplicate specification of transform list file \"" << transform << "\" ignored" << endl;
Chris@0 699 } else {
Chris@0 700 requestedTransformListFiles.insert(transform);
Chris@0 701 }
Chris@0 702 continue;
Chris@0 703 }
Chris@0 704 } else if (arg == "-d" || arg == "--default") {
Chris@0 705 if (last || args[i+1].startsWith("-")) {
Chris@127 706 cerr << myname << ": argument expected for \""
Chris@127 707 << arg << "\" option" << endl;
Chris@127 708 cerr << helpStr << endl;
Chris@0 709 exit(2);
Chris@0 710 } else {
Chris@0 711 string deft = args[++i].toStdString();
Chris@0 712 if (requestedDefaultTransforms.find(deft) !=
Chris@0 713 requestedDefaultTransforms.end()) {
Chris@127 714 cerr << myname << ": NOTE: duplicate specification of default transform \"" << deft << "\" ignored" << endl;
Chris@0 715 } else {
Chris@0 716 requestedDefaultTransforms.insert(deft);
Chris@0 717 }
Chris@0 718 continue;
Chris@0 719 }
Chris@0 720 } else if (arg == "-S" || arg == "--summary") {
Chris@0 721 if (last || args[i+1].startsWith("-")) {
Chris@127 722 cerr << myname << ": argument expected for \""
Chris@127 723 << arg << "\" option" << endl;
Chris@127 724 cerr << helpStr << endl;
Chris@0 725 exit(2);
Chris@0 726 } else {
Chris@0 727 string summary = args[++i].toStdString();
Chris@0 728 requestedSummaryTypes.insert(summary);
Chris@0 729 continue;
Chris@0 730 }
Chris@0 731 } else if (arg == "--summary-only") {
Chris@0 732 summaryOnly = true;
Chris@0 733 continue;
Chris@0 734 } else if (arg == "--segments") {
Chris@0 735 if (last) {
Chris@127 736 cerr << myname << ": argument expected for \""
Chris@127 737 << arg << "\" option" << endl;
Chris@127 738 cerr << helpStr << endl;
Chris@0 739 exit(2);
Chris@0 740 } else {
Chris@0 741 string segmentSpec = args[++i].toStdString();
Chris@0 742 QStringList segmentStrs = QString(segmentSpec.c_str()).split(',');
Chris@0 743 for (int j = 0; j < segmentStrs.size(); ++j) {
Chris@0 744 bool good = false;
Chris@0 745 boundaries.insert(Vamp::RealTime::fromSeconds
Chris@0 746 (segmentStrs[j].toDouble(&good)));
Chris@0 747 if (!good) {
Chris@127 748 cerr << myname << ": segment boundaries must be numeric" << endl;
Chris@127 749 cerr << helpStr << endl;
Chris@0 750 exit(2);
Chris@0 751 }
Chris@0 752 }
Chris@0 753 }
Chris@182 754 } else if (arg == "--segments-from") {
Chris@182 755 if (last) {
Chris@182 756 cerr << myname << ": argument expected for \""
Chris@182 757 << arg << "\" option" << endl;
Chris@182 758 cerr << helpStr << endl;
Chris@182 759 exit(2);
Chris@182 760 } else {
Chris@182 761 QString segmentFilename = args[++i];
Chris@182 762 if (!readSegmentBoundaries(segmentFilename, boundaries)) {
Chris@182 763 cerr << myname << ": failed to read segment boundaries from file" << endl;
Chris@182 764 cerr << helpStr << endl;
Chris@182 765 exit(2);
Chris@182 766 }
Chris@182 767 }
Chris@0 768 } else if (arg == "-m" || arg == "--multiplex") {
Chris@0 769 multiplex = true;
Chris@0 770 continue;
Chris@0 771 } else if (arg == "-r" || arg == "--recursive") {
Chris@0 772 recursive = true;
Chris@0 773 continue;
Chris@116 774 } else if (arg == "-n" || arg == "--normalise") {
Chris@116 775 normalise = true;
Chris@116 776 continue;
Chris@21 777 } else if (arg == "-f" || arg == "--force") {
Chris@21 778 force = true;
Chris@21 779 continue;
Chris@263 780 } else if (arg == "-q" || arg == "--quiet") {
Chris@263 781 quiet = true;
Chris@263 782 continue;
Chris@144 783 } else if (arg == "--list-writers") {
Chris@144 784 listWriters = true;
Chris@144 785 continue;
Chris@144 786 } else if (arg == "--list-formats") {
Chris@144 787 listFormats = true;
Chris@144 788 continue;
Chris@0 789 } else if (arg == "-l" || arg == "--list") {
Chris@0 790 list = true;
Chris@0 791 continue;
Chris@127 792 } else if (arg == "--minversion") {
Chris@127 793 if (last || args[i+1].startsWith("-")) {
Chris@127 794 cerr << myname << ": usage: "
Chris@127 795 << myname << " " << arg << " <version>" << endl;
Chris@127 796 cerr << helpStr << endl;
Chris@127 797 exit(2);
Chris@127 798 }
Chris@127 799 minVersion = args[++i];
Chris@127 800 continue;
Chris@255 801 } else if (arg == "--transform-minversion") {
Chris@255 802 if (last || (i+2) == args.size() ||
Chris@255 803 args[i+1].startsWith("-") ||
Chris@255 804 args[i+2].startsWith("-")) {
Chris@255 805 cerr << myname << ": usage: "
Chris@255 806 << myname << " " << arg << " <version>" << endl;
Chris@255 807 cerr << helpStr << endl;
Chris@255 808 exit(2);
Chris@255 809 }
Chris@255 810 transformMinVersion = { args[i+1], args[i+2] };
Chris@255 811 i += 2;
Chris@255 812 continue;
Chris@0 813 } else if (arg == "-s" || arg == "--skeleton") {
Chris@0 814 if (last || args[i+1].startsWith("-")) {
Chris@127 815 cerr << myname << ": usage: "
Chris@127 816 << myname << " " << arg
Chris@0 817 << " <transform>" << endl;
Chris@127 818 cerr << helpStr << endl;
Chris@0 819 exit(2);
Chris@0 820 } else {
Chris@0 821 skeletonFor = args[++i];
Chris@0 822 continue;
Chris@0 823 }
Chris@0 824 } else {
Chris@0 825 otherArgs.push_back(args[i]);
Chris@0 826 }
Chris@0 827 }
Chris@0 828
Chris@263 829 if (quiet) {
Chris@263 830 SVDebug::silence();
Chris@263 831 SVCerr::silence();
Chris@263 832 }
Chris@263 833
Chris@0 834 if (list) {
Chris@0 835 if (!requestedWriterTags.empty() || skeletonFor != "") {
Chris@127 836 cerr << helpStr << endl;
Chris@0 837 exit(2);
Chris@0 838 }
Chris@0 839 listTransforms();
Chris@0 840 exit(0);
Chris@0 841 }
Chris@144 842 if (listWriters) {
Chris@144 843 if (!requestedWriterTags.empty() || skeletonFor != "") {
Chris@144 844 cerr << helpStr << endl;
Chris@144 845 exit(2);
Chris@144 846 }
Chris@144 847 set<string> writers = FeatureWriterFactory::getWriterTags();
Chris@144 848 bool first = true;
Chris@144 849 for (set<string>::const_iterator i = writers.begin();
Chris@144 850 i != writers.end(); ++i) {
Chris@144 851 if (!first) cout << " ";
Chris@144 852 cout << *i;
Chris@144 853 first = false;
Chris@144 854 }
Chris@144 855 cout << endl;
Chris@144 856 exit(0);
Chris@144 857 }
Chris@144 858 if (listFormats) {
Chris@144 859 if (!requestedWriterTags.empty() || skeletonFor != "") {
Chris@144 860 cerr << helpStr << endl;
Chris@144 861 exit(2);
Chris@144 862 }
Chris@144 863 QString extensions = AudioFileReaderFactory::getKnownExtensions();
Chris@144 864 QStringList extlist = extensions.split(" ", QString::SkipEmptyParts);
Chris@144 865 bool first = true;
Chris@144 866 foreach (QString s, extlist) {
Chris@144 867 if (!first) cout << " ";
Chris@144 868 s.replace("*.", "");
Chris@144 869 cout << s;
Chris@144 870 first = false;
Chris@144 871 }
Chris@144 872 cout << endl;
Chris@144 873 exit(0);
Chris@144 874 }
Chris@144 875 if (list) {
Chris@144 876 if (!requestedWriterTags.empty() || skeletonFor != "") {
Chris@144 877 cerr << helpStr << endl;
Chris@144 878 exit(2);
Chris@144 879 }
Chris@144 880 listTransforms();
Chris@144 881 exit(0);
Chris@144 882 }
Chris@0 883 if (skeletonFor != "") {
Chris@0 884 if (!requestedWriterTags.empty()) {
Chris@127 885 cerr << helpStr << endl;
Chris@0 886 exit(2);
Chris@0 887 }
Chris@0 888 printSkeleton(skeletonFor);
Chris@0 889 exit(0);
Chris@0 890 }
Chris@127 891 if (minVersion != "") {
Chris@127 892 if (!requestedWriterTags.empty()) {
Chris@127 893 cerr << helpStr << endl;
Chris@127 894 exit(2);
Chris@127 895 }
Chris@127 896 exit(checkMinVersion(myname, minVersion));
Chris@127 897 }
Chris@255 898 if (transformMinVersion.first != "") {
Chris@255 899 if (!requestedWriterTags.empty()) {
Chris@255 900 cerr << helpStr << endl;
Chris@255 901 exit(2);
Chris@255 902 }
Chris@255 903 bool ok = false;
Chris@255 904 int version = transformMinVersion.second.toInt(&ok);
Chris@255 905 if (!ok) {
Chris@255 906 cerr << myname << ": plugin version \"" << transformMinVersion.second
Chris@255 907 << "\" must be an integer" << endl;
Chris@255 908 cerr << helpStr << endl;
Chris@255 909 exit(2);
Chris@255 910 }
Chris@255 911 exit(checkTransformMinVersion(myname, transformMinVersion.first, version));
Chris@255 912 }
Chris@0 913
Chris@0 914 if (requestedTransformFiles.empty() &&
Chris@0 915 requestedTransformListFiles.empty() &&
Chris@0 916 requestedDefaultTransforms.empty()) {
Chris@127 917 cerr << myname
Chris@0 918 << ": no transform(s) specified" << endl;
Chris@127 919 cerr << helpStr << endl;
Chris@0 920 exit(2);
Chris@0 921 }
Chris@0 922
Chris@0 923 if (requestedWriterTags.empty()) {
Chris@127 924 cerr << myname
Chris@0 925 << ": no writer(s) specified" << endl;
Chris@127 926 cerr << helpStr << endl;
Chris@0 927 exit(2);
Chris@0 928 }
Chris@0 929
Chris@0 930 if (!boundaries.empty()) {
Chris@0 931 if (requestedSummaryTypes.empty()) {
Chris@127 932 cerr << myname
Chris@0 933 << ": summary segment boundaries provided, but no summary type specified"
Chris@0 934 << endl;
Chris@127 935 cerr << helpStr << endl;
Chris@0 936 exit(2);
Chris@0 937 }
Chris@0 938 }
Chris@0 939
Chris@20 940 QSettings settings;
Chris@20 941
Chris@20 942 settings.beginGroup("RDF");
Chris@20 943 if (!settings.contains("rdf-indices")) {
Chris@20 944 QStringList list;
Chris@20 945 list << "http://www.vamp-plugins.org/rdf/plugins/index.txt";
Chris@20 946 settings.setValue("rdf-indices", list);
Chris@20 947 }
Chris@20 948 settings.endGroup();
Chris@20 949
Chris@263 950 FeatureExtractionManager manager(!quiet);
Chris@0 951
Chris@116 952 manager.setNormalise(normalise);
Chris@116 953
Chris@0 954 if (!requestedSummaryTypes.empty()) {
Chris@0 955 if (!manager.setSummaryTypes(requestedSummaryTypes,
Chris@0 956 boundaries)) {
Chris@233 957 cerr << myname << ": failed to set requested summary types" << endl;
Chris@0 958 exit(1);
Chris@0 959 }
Chris@0 960 }
Chris@102 961
Chris@102 962 manager.setSummariesOnly(summaryOnly);
Chris@116 963
Chris@0 964 vector<FeatureWriter *> writers;
Chris@0 965
Chris@0 966 for (set<string>::const_iterator i = requestedWriterTags.begin();
Chris@0 967 i != requestedWriterTags.end(); ++i) {
Chris@0 968
Chris@0 969 FeatureWriter *writer = FeatureWriterFactory::createWriter(*i);
Chris@0 970
Chris@0 971 if (!writer) {
Chris@127 972 cerr << myname << ": unknown feature writer \""
Chris@0 973 << *i << "\"" << endl;
Chris@127 974 cerr << helpStr << endl;
Chris@0 975 exit(2);
Chris@0 976 }
Chris@0 977
Chris@0 978 map<string, string> writerArgs;
Chris@0 979 FeatureWriter::ParameterList pl(writer->getSupportedParameters());
Chris@0 980
Chris@95 981 for (int k = 0; k < (int)pl.size(); ++k) {
Chris@0 982
Chris@0 983 string argbase = pl[k].name;
Chris@0 984 QString literal = QString("--%1-%2")
Chris@0 985 .arg(i->c_str()).arg(argbase.c_str());
Chris@0 986
Chris@95 987 for (int j = 0; j < (int)otherArgs.size(); ) {
Chris@0 988
Chris@0 989 if (otherArgs[j] != literal) {
Chris@0 990 ++j;
Chris@0 991 continue;
Chris@0 992 }
Chris@0 993
Chris@0 994 otherArgs.removeAt(j);
Chris@0 995
Chris@0 996 if (pl[k].hasArg) {
Chris@0 997 if (j < otherArgs.size()) {
Chris@0 998 writerArgs[argbase] = otherArgs[j].toStdString();
Chris@0 999 otherArgs.removeAt(j);
Chris@0 1000 } else {
Chris@127 1001 cerr << myname << ": "
Chris@0 1002 << "argument required for \""
Chris@127 1003 << literal << "\" option"
Chris@0 1004 << endl;
Chris@127 1005 cerr << helpStr << endl;
Chris@0 1006 exit(2);
Chris@0 1007 }
Chris@0 1008 } else {
Chris@0 1009 writerArgs[argbase] = "";
Chris@0 1010 }
Chris@0 1011 }
Chris@0 1012 }
Chris@197 1013
Chris@197 1014 for (auto &p: pl) {
Chris@197 1015 if (p.mandatory) {
Chris@197 1016 bool found = false;
Chris@197 1017 for (auto &w: writerArgs) {
Chris@197 1018 if (w.first == p.name) {
Chris@197 1019 found = true;
Chris@197 1020 break;
Chris@197 1021 }
Chris@197 1022 }
Chris@197 1023 if (!found) {
Chris@197 1024 QString literal = QString("--%1-%2")
Chris@197 1025 .arg(i->c_str()).arg(p.name.c_str());
Chris@197 1026 cerr << myname << ": "
Chris@197 1027 << "the \"" << literal << "\" parameter is mandatory"
Chris@197 1028 << endl;
Chris@197 1029 cerr << helpStr << endl;
Chris@197 1030 exit(2);
Chris@197 1031 }
Chris@197 1032 }
Chris@197 1033 }
Chris@197 1034
Chris@197 1035 try {
Chris@197 1036 writer->setParameters(writerArgs);
Chris@197 1037 } catch (std::exception &ex) {
Chris@197 1038 cerr << myname << ": " << ex.what() << endl;
Chris@197 1039 cerr << helpStr << endl;
Chris@197 1040 exit(2);
Chris@197 1041 }
Chris@0 1042
Chris@0 1043 writers.push_back(writer);
Chris@0 1044 }
Chris@0 1045
Chris@0 1046 for (int i = 0; i < otherArgs.size(); ++i) {
Chris@0 1047 if (otherArgs[i].startsWith("-")) {
Chris@127 1048 cerr << myname << ": unknown option \""
Chris@127 1049 << otherArgs[i] << "\"" << endl;
Chris@127 1050 cerr << helpStr << endl;
Chris@0 1051 exit(2);
Chris@0 1052 }
Chris@0 1053 }
Chris@0 1054
Chris@0 1055 if (otherArgs.empty()) {
Chris@127 1056 cerr << myname << ": no input(s) specified" << endl;
Chris@127 1057 cerr << helpStr << endl;
Chris@0 1058 exit(2);
Chris@0 1059 }
Chris@0 1060
Chris@0 1061 for (set<string>::const_iterator i = requestedTransformListFiles.begin();
Chris@0 1062 i != requestedTransformListFiles.end(); ++i) {
Chris@265 1063 SVDEBUG << "Reading transform list file \"" << *i << "\"" << endl;
Chris@0 1064 PlaylistFileReader reader(i->c_str());
Chris@0 1065 if (reader.isOK()) {
Chris@0 1066 vector<QString> files = reader.load();
Chris@95 1067 for (int j = 0; j < (int)files.size(); ++j) {
Chris@0 1068 requestedTransformFiles.insert(files[j].toStdString());
Chris@0 1069 }
Chris@0 1070 } else {
Chris@265 1071 SVCERR << myname << ": failed to read transform list file \"" << *i << "\"" << endl;
Chris@0 1072 exit(2);
Chris@0 1073 }
Chris@0 1074 }
Chris@0 1075
Chris@0 1076 QStringList sources;
Chris@0 1077 if (!recursive) {
Chris@0 1078 sources = otherArgs;
Chris@0 1079 } else {
Chris@0 1080 for (QStringList::const_iterator i = otherArgs.begin();
Chris@0 1081 i != otherArgs.end(); ++i) {
Chris@0 1082 if (QDir(*i).exists()) {
Chris@265 1083 SVCERR << "Directory found and recursive flag set, scanning for audio files..." << endl;
Chris@0 1084 int found = 0;
Chris@0 1085 findSourcesRecursive(*i, sources, found);
Chris@265 1086 SVCERR << "\rDone, found " << found << " supported audio file(s) " << endl;
Chris@0 1087 } else {
Chris@0 1088 sources.push_back(*i);
Chris@0 1089 }
Chris@0 1090 }
Chris@0 1091 }
Chris@0 1092
Chris@111 1093 sources = expandPlaylists(sources);
Chris@117 1094
Chris@28 1095 bool good = true;
Chris@45 1096 QSet<QString> badSources;
Chris@28 1097
Chris@0 1098 for (QStringList::const_iterator i = sources.begin();
Chris@0 1099 i != sources.end(); ++i) {
Chris@0 1100 try {
Chris@106 1101 manager.addSource(*i, multiplex);
Chris@22 1102 } catch (const std::exception &e) {
Chris@45 1103 badSources.insert(*i);
Chris@265 1104 SVCERR << "ERROR: Failed to process file \"" << i->toStdString()
Chris@21 1105 << "\": " << e.what() << endl;
Chris@22 1106 if (force) {
Chris@22 1107 // print a note only if we have more files to process
Chris@22 1108 QStringList::const_iterator j = i;
Chris@22 1109 if (++j != sources.end()) {
Chris@265 1110 SVCERR << "NOTE: \"--force\" option was provided, continuing (more errors may occur)" << endl;
Chris@22 1111 }
Chris@22 1112 } else {
Chris@265 1113 SVCERR << "NOTE: If you want to continue with processing any further files after an" << endl
Chris@45 1114 << "error like this, use the --force option" << endl;
Chris@28 1115 good = false;
Chris@22 1116 break;
Chris@22 1117 }
Chris@0 1118 }
Chris@0 1119 }
Chris@45 1120
Chris@45 1121 if (good) {
Chris@45 1122
Chris@45 1123 bool haveFeatureExtractor = false;
Chris@45 1124
Chris@45 1125 for (set<string>::const_iterator i = requestedTransformFiles.begin();
Chris@45 1126 i != requestedTransformFiles.end(); ++i) {
Chris@45 1127 if (manager.addFeatureExtractorFromFile(i->c_str(), writers)) {
Chris@45 1128 haveFeatureExtractor = true;
Chris@120 1129 } else {
Chris@265 1130 SVCERR << "ERROR: Failed to add feature extractor from transform file \"" << *i << "\"" << endl;
Chris@120 1131 good = false;
Chris@45 1132 }
Chris@45 1133 }
Chris@45 1134
Chris@45 1135 for (set<string>::const_iterator i = requestedDefaultTransforms.begin();
Chris@45 1136 i != requestedDefaultTransforms.end(); ++i) {
Chris@45 1137 if (manager.addDefaultFeatureExtractor(i->c_str(), writers)) {
Chris@45 1138 haveFeatureExtractor = true;
Chris@120 1139 } else {
Chris@265 1140 SVCERR << "ERROR: Failed to add default feature extractor for transform \"" << *i << "\"" << endl;
Chris@120 1141 good = false;
Chris@45 1142 }
Chris@45 1143 }
Chris@45 1144
Chris@45 1145 if (!haveFeatureExtractor) {
Chris@265 1146 SVCERR << myname << ": no feature extractors added" << endl;
Chris@45 1147 good = false;
Chris@45 1148 }
Chris@45 1149 }
Chris@45 1150
Chris@45 1151 if (good) {
Chris@106 1152 QStringList goodSources;
Chris@106 1153 foreach (QString source, sources) {
Chris@106 1154 if (!badSources.contains(source)) {
Chris@106 1155 goodSources.push_back(source);
Chris@106 1156 }
Chris@106 1157 }
Chris@106 1158 if (multiplex) {
Chris@45 1159 try {
Chris@169 1160 for (int i = 0; i < (int)writers.size(); ++i) {
Chris@169 1161 writers[i]->setNofM(1, 1);
Chris@169 1162 }
Chris@106 1163 manager.extractFeaturesMultiplexed(goodSources);
Chris@45 1164 } catch (const std::exception &e) {
Chris@265 1165 SVCERR << "ERROR: Feature extraction failed: "
Chris@106 1166 << e.what() << endl;
Chris@106 1167 }
Chris@106 1168 } else {
Chris@169 1169 int n = 0;
Chris@106 1170 for (QStringList::const_iterator i = goodSources.begin();
Chris@106 1171 i != goodSources.end(); ++i) {
Chris@263 1172 SVCERR << "Extracting features for: \"" << *i << "\"" << endl;
Chris@169 1173 ++n;
Chris@106 1174 try {
Chris@169 1175 for (int j = 0; j < (int)writers.size(); ++j) {
Chris@169 1176 writers[j]->setNofM(n, goodSources.size());
Chris@169 1177 }
Chris@112 1178 manager.extractFeatures(*i);
Chris@106 1179 } catch (const std::exception &e) {
Chris@263 1180 SVCERR << "ERROR: Feature extraction failed for \""
Chris@263 1181 << i->toStdString() << "\": " << e.what() << endl;
Chris@106 1182 if (force) {
Chris@106 1183 // print a note only if we have more files to process
Chris@106 1184 QStringList::const_iterator j = i;
Chris@106 1185 if (++j != sources.end()) {
Chris@263 1186 SVCERR << "NOTE: \"--force\" option was provided, continuing (more errors may occur)" << endl;
Chris@106 1187 }
Chris@106 1188 } else {
Chris@263 1189 SVCERR << "NOTE: If you want to continue with processing any further files after an" << endl
Chris@263 1190 << "error like this, use the --force option" << endl;
Chris@106 1191 good = false;
Chris@106 1192 break;
Chris@45 1193 }
Chris@45 1194 }
Chris@45 1195 }
Chris@45 1196 }
Chris@45 1197 }
Chris@0 1198
Chris@95 1199 for (int i = 0; i < (int)writers.size(); ++i) delete writers[i];
Chris@0 1200
Chris@0 1201 TempDirectory::getInstance()->cleanup();
Chris@0 1202
Chris@28 1203 if (good) return 0;
Chris@28 1204 else return 1;
Chris@0 1205 }
Chris@0 1206
Chris@0 1207