Mercurial > hg > sonic-annotator
comparison runner/main.cpp @ 1:92911f967a16
* some reorganisation
author | Chris Cannam |
---|---|
date | Thu, 11 Dec 2008 10:26:12 +0000 |
parents | main.cpp@581b1b150a4d |
children | 475f4623feba |
comparison
equal
deleted
inserted
replaced
0:581b1b150a4d | 1:92911f967a16 |
---|---|
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 |