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