annotate runner/main.cpp @ 399:a3912193ce69 tip

Default branch is now named default on git as well as hg, in case we ever want to switch to mirroring in the other direction
author Chris Cannam
date Thu, 27 Aug 2020 15:57:37 +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