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