comparison main.cpp @ 0:581b1b150a4d

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