Chris@498
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@498
|
2
|
Chris@498
|
3 /*
|
Chris@498
|
4 Sonic Visualiser
|
Chris@498
|
5 An audio file viewer and annotation editor.
|
Chris@498
|
6
|
Chris@498
|
7 Sonic Annotator
|
Chris@498
|
8 A utility for batch feature extraction from audio files.
|
Chris@498
|
9
|
Chris@498
|
10 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
|
Chris@498
|
11 Copyright 2007-2008 QMUL.
|
Chris@498
|
12
|
Chris@498
|
13 This program is free software; you can redistribute it and/or
|
Chris@498
|
14 modify it under the terms of the GNU General Public License as
|
Chris@498
|
15 published by the Free Software Foundation; either version 2 of the
|
Chris@498
|
16 License, or (at your option) any later version. See the file
|
Chris@498
|
17 COPYING included with this distribution for more information.
|
Chris@498
|
18 */
|
Chris@498
|
19
|
Chris@498
|
20 #include "FileFeatureWriter.h"
|
Chris@498
|
21
|
Chris@498
|
22 #include "base/Exceptions.h"
|
Chris@498
|
23
|
Chris@498
|
24 #include <QTextStream>
|
Chris@498
|
25 #include <QFile>
|
Chris@498
|
26 #include <QFileInfo>
|
Chris@498
|
27 #include <QUrl>
|
Chris@498
|
28 #include <QDir>
|
Chris@498
|
29
|
Chris@498
|
30 using namespace std;
|
Chris@498
|
31 using namespace Vamp;
|
Chris@498
|
32
|
Chris@498
|
33 FileFeatureWriter::FileFeatureWriter(int support,
|
Chris@498
|
34 QString extension) :
|
Chris@512
|
35 m_prevstream(0),
|
Chris@498
|
36 m_support(support),
|
Chris@498
|
37 m_extension(extension),
|
Chris@498
|
38 m_manyFiles(false),
|
Chris@498
|
39 m_stdout(false),
|
Chris@498
|
40 m_append(false),
|
Chris@498
|
41 m_force(false)
|
Chris@498
|
42 {
|
Chris@498
|
43 if (!(m_support & SupportOneFilePerTrack)) {
|
Chris@498
|
44 if (m_support & SupportOneFilePerTrackTransform) {
|
Chris@498
|
45 m_manyFiles = true;
|
Chris@498
|
46 } else if (m_support & SupportOneFileTotal) {
|
Chris@498
|
47 m_singleFileName = QString("output.%1").arg(m_extension);
|
Chris@498
|
48 } else {
|
Chris@690
|
49 SVDEBUG << "FileFeatureWriter::FileFeatureWriter: ERROR: Invalid support specification " << support << endl;
|
Chris@498
|
50 }
|
Chris@498
|
51 }
|
Chris@498
|
52 }
|
Chris@498
|
53
|
Chris@498
|
54 FileFeatureWriter::~FileFeatureWriter()
|
Chris@498
|
55 {
|
Chris@498
|
56 while (!m_streams.empty()) {
|
Chris@498
|
57 m_streams.begin()->second->flush();
|
Chris@498
|
58 delete m_streams.begin()->second;
|
Chris@498
|
59 m_streams.erase(m_streams.begin());
|
Chris@498
|
60 }
|
Chris@498
|
61 while (!m_files.empty()) {
|
Chris@625
|
62 if (m_files.begin()->second) {
|
Chris@690
|
63 SVDEBUG << "FileFeatureWriter::~FileFeatureWriter: NOTE: Closing feature file \""
|
Chris@686
|
64 << m_files.begin()->second->fileName() << "\"" << endl;
|
Chris@625
|
65 delete m_files.begin()->second;
|
Chris@625
|
66 }
|
Chris@498
|
67 m_files.erase(m_files.begin());
|
Chris@498
|
68 }
|
Chris@498
|
69 }
|
Chris@498
|
70
|
Chris@498
|
71 FileFeatureWriter::ParameterList
|
Chris@498
|
72 FileFeatureWriter::getSupportedParameters() const
|
Chris@498
|
73 {
|
Chris@498
|
74 ParameterList pl;
|
Chris@498
|
75 Parameter p;
|
Chris@498
|
76
|
Chris@498
|
77 p.name = "basedir";
|
Chris@999
|
78 p.description = "Base output directory path. (The default is the same directory as the input file.) The directory must exist already.";
|
Chris@498
|
79 p.hasArg = true;
|
Chris@498
|
80 pl.push_back(p);
|
Chris@498
|
81
|
Chris@498
|
82 if (m_support & SupportOneFilePerTrackTransform &&
|
Chris@498
|
83 m_support & SupportOneFilePerTrack) {
|
Chris@498
|
84 p.name = "many-files";
|
Chris@999
|
85 p.description = "Create a separate output file for every combination of input file and transform. The output file names will be based on the input file names. (The default is to create one output file per input audio file, and write all transform results for that input into it.)";
|
Chris@498
|
86 p.hasArg = false;
|
Chris@498
|
87 pl.push_back(p);
|
Chris@498
|
88 }
|
Chris@498
|
89
|
Chris@498
|
90 if (m_support & SupportOneFileTotal) {
|
Chris@498
|
91 if (m_support & ~SupportOneFileTotal) { // not only option
|
Chris@498
|
92 p.name = "one-file";
|
Chris@659
|
93 if (m_support & SupportOneFilePerTrack) {
|
Chris@999
|
94 p.description = "Write all transform results for all input files into the single named output file. (The default is to create one output file per input audio file, and write all transform results for that input into it.)";
|
Chris@659
|
95 } else {
|
Chris@999
|
96 p.description = "Write all transform results for all input files into the single named output file. (The default is to create a separate output file for each combination of input audio file and transform.)";
|
Chris@659
|
97 }
|
Chris@498
|
98 p.hasArg = true;
|
Chris@498
|
99 pl.push_back(p);
|
Chris@498
|
100 }
|
Chris@997
|
101 }
|
Chris@997
|
102 if (m_support & SupportStdOut) {
|
Chris@498
|
103 p.name = "stdout";
|
Chris@498
|
104 p.description = "Write all transform results directly to standard output.";
|
Chris@498
|
105 p.hasArg = false;
|
Chris@498
|
106 pl.push_back(p);
|
Chris@498
|
107 }
|
Chris@498
|
108
|
Chris@498
|
109 p.name = "force";
|
Chris@498
|
110 p.description = "If an output file already exists, overwrite it.";
|
Chris@498
|
111 p.hasArg = false;
|
Chris@498
|
112 pl.push_back(p);
|
Chris@498
|
113
|
Chris@498
|
114 p.name = "append";
|
Chris@498
|
115 p.description = "If an output file already exists, append data to it.";
|
Chris@498
|
116 p.hasArg = false;
|
Chris@498
|
117 pl.push_back(p);
|
Chris@498
|
118
|
Chris@498
|
119 return pl;
|
Chris@498
|
120 }
|
Chris@498
|
121
|
Chris@498
|
122 void
|
Chris@498
|
123 FileFeatureWriter::setParameters(map<string, string> ¶ms)
|
Chris@498
|
124 {
|
Chris@498
|
125 for (map<string, string>::iterator i = params.begin();
|
Chris@498
|
126 i != params.end(); ++i) {
|
Chris@498
|
127 if (i->first == "basedir") {
|
Chris@498
|
128 m_baseDir = i->second.c_str();
|
Chris@498
|
129 } else if (i->first == "many-files") {
|
Chris@498
|
130 if (m_support & SupportOneFilePerTrackTransform &&
|
Chris@498
|
131 m_support & SupportOneFilePerTrack) {
|
Chris@498
|
132 if (m_singleFileName != "") {
|
Chris@690
|
133 SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both one-file and many-files parameters provided, ignoring many-files" << endl;
|
Chris@498
|
134 } else {
|
Chris@498
|
135 m_manyFiles = true;
|
Chris@498
|
136 }
|
Chris@498
|
137 }
|
Chris@498
|
138 } else if (i->first == "one-file") {
|
Chris@498
|
139 if (m_support & SupportOneFileTotal) {
|
Chris@498
|
140 if (m_support & ~SupportOneFileTotal) { // not only option
|
Chris@659
|
141 // No, we cannot do this test because m_manyFiles
|
Chris@659
|
142 // may be on by default (for any FileFeatureWriter
|
Chris@659
|
143 // that supports OneFilePerTrackTransform but not
|
Chris@659
|
144 // OneFilePerTrack), so we need to be able to
|
Chris@659
|
145 // override it
|
Chris@659
|
146 // if (m_manyFiles) {
|
Chris@690
|
147 // SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both many-files and one-file parameters provided, ignoring one-file" << endl;
|
Chris@659
|
148 // } else {
|
Chris@498
|
149 m_singleFileName = i->second.c_str();
|
Chris@659
|
150 // }
|
Chris@498
|
151 }
|
Chris@498
|
152 }
|
Chris@498
|
153 } else if (i->first == "stdout") {
|
Chris@997
|
154 if (m_support & SupportStdOut) {
|
Chris@498
|
155 if (m_singleFileName != "") {
|
Chris@690
|
156 SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both stdout and one-file provided, ignoring stdout" << endl;
|
Chris@498
|
157 } else {
|
Chris@498
|
158 m_stdout = true;
|
Chris@498
|
159 }
|
Chris@498
|
160 }
|
Chris@498
|
161 } else if (i->first == "append") {
|
Chris@498
|
162 m_append = true;
|
Chris@498
|
163 } else if (i->first == "force") {
|
Chris@498
|
164 m_force = true;
|
Chris@498
|
165 }
|
Chris@498
|
166 }
|
Chris@498
|
167 }
|
Chris@498
|
168
|
Chris@625
|
169 QString
|
Chris@999
|
170 FileFeatureWriter::createOutputFilename(QString trackId,
|
Chris@999
|
171 TransformId transformId)
|
Chris@498
|
172 {
|
Chris@498
|
173 if (m_singleFileName != "") {
|
Chris@498
|
174 if (QFileInfo(m_singleFileName).exists() && !(m_force || m_append)) {
|
Chris@686
|
175 cerr << endl << "FileFeatureWriter: ERROR: Specified output file \"" << m_singleFileName << "\" exists and neither --" << getWriterTag() << "-force nor --" << getWriterTag() << "-append flag is specified -- not overwriting" << endl;
|
Chris@690
|
176 SVDEBUG << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
|
Chris@498
|
177 return "";
|
Chris@498
|
178 }
|
Chris@498
|
179 return m_singleFileName;
|
Chris@498
|
180 }
|
Chris@498
|
181
|
Chris@999
|
182 if (m_stdout) {
|
Chris@999
|
183 return "";
|
Chris@999
|
184 }
|
Chris@498
|
185
|
Christophe@615
|
186 QUrl url(trackId, QUrl::StrictMode);
|
Chris@498
|
187 QString scheme = url.scheme().toLower();
|
Chris@498
|
188 bool local = (scheme == "" || scheme == "file" || scheme.length() == 1);
|
Chris@498
|
189
|
Chris@498
|
190 QString dirname, basename;
|
Chris@498
|
191 QString infilename = url.toLocalFile();
|
Chris@519
|
192 if (infilename == "") {
|
Chris@519
|
193 infilename = url.path();
|
Chris@519
|
194 }
|
Christophe@615
|
195 basename = QFileInfo(infilename).completeBaseName();
|
Chris@519
|
196 if (scheme.length() == 1) {
|
Chris@519
|
197 infilename = scheme + ":" + infilename; // DOS drive!
|
Chris@519
|
198 }
|
Chris@507
|
199
|
Chris@686
|
200 // cerr << "trackId = " << trackId << ", url = " << url.toString() << ", infilename = "
|
Chris@686
|
201 // << infilename << ", basename = " << basename << ", m_baseDir = " << m_baseDir << endl;
|
Chris@498
|
202
|
Chris@498
|
203 if (m_baseDir != "") dirname = QFileInfo(m_baseDir).absoluteFilePath();
|
Chris@498
|
204 else if (local) dirname = QFileInfo(infilename).absolutePath();
|
Chris@498
|
205 else dirname = QDir::currentPath();
|
Chris@498
|
206
|
Chris@686
|
207 // cerr << "dirname = " << dirname << endl;
|
Chris@604
|
208
|
Chris@498
|
209 QString filename;
|
Chris@498
|
210
|
Chris@498
|
211 if (m_manyFiles && transformId != "") {
|
Chris@514
|
212 filename = QString("%1_%2.%3").arg(basename).arg(transformId).arg(m_extension);
|
Chris@498
|
213 } else {
|
Chris@498
|
214 filename = QString("%1.%2").arg(basename).arg(m_extension);
|
Chris@498
|
215 }
|
Chris@498
|
216
|
Chris@514
|
217 filename.replace(':', '_'); // ':' not permitted in Windows
|
Chris@514
|
218
|
Chris@498
|
219 filename = QDir(dirname).filePath(filename);
|
Chris@498
|
220
|
Chris@498
|
221 if (QFileInfo(filename).exists() && !(m_force || m_append)) {
|
Chris@686
|
222 cerr << endl << "FileFeatureWriter: ERROR: Output file \"" << filename << "\" exists (for input file or URL \"" << trackId << "\" and transform \"" << transformId << "\") and neither --" << getWriterTag() << "-force nor --" << getWriterTag() << "-append is specified -- not overwriting" << endl;
|
Chris@690
|
223 SVDEBUG << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
|
Chris@498
|
224 return "";
|
Chris@498
|
225 }
|
Chris@498
|
226
|
Chris@498
|
227 return filename;
|
Chris@498
|
228 }
|
Chris@498
|
229
|
Chris@625
|
230 void
|
Chris@625
|
231 FileFeatureWriter::testOutputFile(QString trackId,
|
Chris@625
|
232 TransformId transformId)
|
Chris@625
|
233 {
|
Chris@626
|
234 // Obviously, if we're writing to stdout we can't test for an
|
Chris@626
|
235 // openable output file. But when writing a single file we don't
|
Chris@626
|
236 // want to either, because this test would fail on the second and
|
Chris@626
|
237 // subsequent input files (because the file would already exist).
|
Chris@626
|
238 // getOutputFile does the right thing in this case, so we just
|
Chris@626
|
239 // leave it to it
|
Chris@626
|
240 if (m_stdout || m_singleFileName != "") return;
|
Chris@626
|
241
|
Chris@999
|
242 QString filename = createOutputFilename(trackId, transformId);
|
Chris@625
|
243 if (filename == "") {
|
Chris@625
|
244 throw FailedToOpenOutputStream(trackId, transformId);
|
Chris@625
|
245 }
|
Chris@625
|
246 }
|
Chris@498
|
247
|
Chris@999
|
248 FileFeatureWriter::TrackTransformPair
|
Chris@999
|
249 FileFeatureWriter::getFilenameKey(QString trackId,
|
Chris@999
|
250 TransformId transformId)
|
Chris@999
|
251 {
|
Chris@999
|
252 TrackTransformPair key;
|
Chris@999
|
253
|
Chris@999
|
254 if (m_singleFileName != "") {
|
Chris@999
|
255 key = TrackTransformPair("", "");
|
Chris@999
|
256 } else if (m_manyFiles) {
|
Chris@999
|
257 key = TrackTransformPair(trackId, transformId);
|
Chris@999
|
258 } else {
|
Chris@999
|
259 key = TrackTransformPair(trackId, "");
|
Chris@999
|
260 }
|
Chris@999
|
261
|
Chris@999
|
262 return key;
|
Chris@999
|
263 }
|
Chris@999
|
264
|
Chris@999
|
265 QString
|
Chris@999
|
266 FileFeatureWriter::getOutputFilename(QString trackId,
|
Chris@999
|
267 TransformId transformId)
|
Chris@999
|
268 {
|
Chris@999
|
269 TrackTransformPair key = getFilenameKey(trackId, transformId);
|
Chris@999
|
270 if (m_filenames.find(key) == m_filenames.end()) {
|
Chris@999
|
271 m_filenames[key] = createOutputFilename(trackId, transformId);
|
Chris@999
|
272 }
|
Chris@999
|
273 return m_filenames[key];
|
Chris@999
|
274 }
|
Chris@999
|
275
|
Chris@625
|
276 QFile *
|
Chris@625
|
277 FileFeatureWriter::getOutputFile(QString trackId,
|
Chris@625
|
278 TransformId transformId)
|
Chris@498
|
279 {
|
Chris@999
|
280 TrackTransformPair key = getFilenameKey(trackId, transformId);
|
Chris@498
|
281
|
Chris@498
|
282 if (m_files.find(key) == m_files.end()) {
|
Chris@498
|
283
|
Chris@999
|
284 QString filename = createOutputFilename(trackId, transformId);
|
Chris@498
|
285
|
Chris@625
|
286 if (filename == "") { // stdout or failure
|
Chris@498
|
287 return 0;
|
Chris@498
|
288 }
|
Chris@498
|
289
|
Chris@690
|
290 SVDEBUG << "FileFeatureWriter: NOTE: Using output filename \""
|
Chris@686
|
291 << filename << "\"" << endl;
|
Chris@498
|
292
|
Chris@591
|
293 if (m_append) {
|
Chris@690
|
294 SVDEBUG << "FileFeatureWriter: NOTE: Calling reviewFileForAppending" << endl;
|
Chris@591
|
295 reviewFileForAppending(filename);
|
Chris@591
|
296 }
|
Chris@591
|
297
|
Chris@498
|
298 QFile *file = new QFile(filename);
|
Chris@498
|
299 QIODevice::OpenMode mode = (QIODevice::WriteOnly);
|
Chris@498
|
300 if (m_append) mode |= QIODevice::Append;
|
Chris@498
|
301
|
Chris@498
|
302 if (!file->open(mode)) {
|
Chris@844
|
303 cerr << "FileFeatureWriter: ERROR: Failed to open output file \"" << filename
|
Chris@498
|
304 << "\" for writing" << endl;
|
Chris@498
|
305 delete file;
|
Chris@498
|
306 m_files[key] = 0;
|
Chris@498
|
307 throw FailedToOpenFile(filename);
|
Chris@498
|
308 }
|
Chris@626
|
309
|
Chris@498
|
310 m_files[key] = file;
|
Chris@498
|
311 }
|
Chris@498
|
312
|
Chris@498
|
313 return m_files[key];
|
Chris@498
|
314 }
|
Chris@498
|
315
|
Chris@498
|
316
|
Chris@498
|
317 QTextStream *FileFeatureWriter::getOutputStream(QString trackId,
|
Chris@498
|
318 TransformId transformId)
|
Chris@498
|
319 {
|
Chris@498
|
320 QFile *file = getOutputFile(trackId, transformId);
|
Chris@498
|
321 if (!file && !m_stdout) {
|
Chris@498
|
322 return 0;
|
Chris@498
|
323 }
|
Chris@626
|
324
|
Chris@498
|
325 if (m_streams.find(file) == m_streams.end()) {
|
Chris@498
|
326 if (m_stdout) {
|
Chris@498
|
327 m_streams[file] = new QTextStream(stdout);
|
Chris@498
|
328 } else {
|
Chris@498
|
329 m_streams[file] = new QTextStream(file);
|
Chris@498
|
330 }
|
Chris@498
|
331 }
|
Chris@498
|
332
|
Chris@512
|
333 QTextStream *stream = m_streams[file];
|
Chris@512
|
334
|
Chris@512
|
335 if (m_prevstream && stream != m_prevstream) {
|
Chris@512
|
336 m_prevstream->flush();
|
Chris@512
|
337 }
|
Chris@512
|
338 m_prevstream = stream;
|
Chris@512
|
339
|
Chris@512
|
340 return stream;
|
Chris@498
|
341 }
|
Chris@498
|
342
|
Chris@515
|
343
|
Chris@515
|
344 void
|
Chris@515
|
345 FileFeatureWriter::flush()
|
Chris@515
|
346 {
|
Chris@515
|
347 if (m_prevstream) {
|
Chris@515
|
348 m_prevstream->flush();
|
Chris@515
|
349 }
|
Chris@515
|
350 }
|
Chris@515
|
351
|
Chris@531
|
352
|
Chris@531
|
353 void
|
Chris@531
|
354 FileFeatureWriter::finish()
|
Chris@531
|
355 {
|
Chris@690
|
356 // SVDEBUG << "FileFeatureWriter::finish()" << endl;
|
Chris@531
|
357
|
Chris@531
|
358 if (m_singleFileName != "" || m_stdout) return;
|
Chris@531
|
359
|
Chris@531
|
360 while (!m_streams.empty()) {
|
Chris@531
|
361 m_streams.begin()->second->flush();
|
Chris@531
|
362 delete m_streams.begin()->second;
|
Chris@531
|
363 m_streams.erase(m_streams.begin());
|
Chris@531
|
364 }
|
Chris@531
|
365 while (!m_files.empty()) {
|
Chris@625
|
366 if (m_files.begin()->second) {
|
Chris@690
|
367 SVDEBUG << "FileFeatureWriter::finish: NOTE: Closing feature file \""
|
Chris@686
|
368 << m_files.begin()->second->fileName() << "\"" << endl;
|
Chris@625
|
369 delete m_files.begin()->second;
|
Chris@625
|
370 }
|
Chris@531
|
371 m_files.erase(m_files.begin());
|
Chris@531
|
372 }
|
Chris@531
|
373 m_prevstream = 0;
|
Chris@531
|
374 }
|
Chris@531
|
375
|