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