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@1140
|
49 cerr << "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@1140
|
133 cerr << "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@1140
|
147 // cerr << "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@1140
|
156 cerr << "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@1140
|
176 cerr << "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@1140
|
223 cerr << "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@1035
|
318 TransformId transformId,
|
Chris@1035
|
319 QTextCodec *codec)
|
Chris@498
|
320 {
|
Chris@498
|
321 QFile *file = getOutputFile(trackId, transformId);
|
Chris@498
|
322 if (!file && !m_stdout) {
|
Chris@498
|
323 return 0;
|
Chris@498
|
324 }
|
Chris@626
|
325
|
Chris@498
|
326 if (m_streams.find(file) == m_streams.end()) {
|
Chris@498
|
327 if (m_stdout) {
|
Chris@498
|
328 m_streams[file] = new QTextStream(stdout);
|
Chris@498
|
329 } else {
|
Chris@498
|
330 m_streams[file] = new QTextStream(file);
|
Chris@498
|
331 }
|
Chris@1035
|
332 m_streams[file]->setCodec(codec);
|
Chris@498
|
333 }
|
Chris@498
|
334
|
Chris@512
|
335 QTextStream *stream = m_streams[file];
|
Chris@512
|
336
|
Chris@512
|
337 if (m_prevstream && stream != m_prevstream) {
|
Chris@512
|
338 m_prevstream->flush();
|
Chris@512
|
339 }
|
Chris@512
|
340 m_prevstream = stream;
|
Chris@512
|
341
|
Chris@512
|
342 return stream;
|
Chris@498
|
343 }
|
Chris@498
|
344
|
Chris@515
|
345
|
Chris@515
|
346 void
|
Chris@515
|
347 FileFeatureWriter::flush()
|
Chris@515
|
348 {
|
Chris@515
|
349 if (m_prevstream) {
|
Chris@515
|
350 m_prevstream->flush();
|
Chris@515
|
351 }
|
Chris@515
|
352 }
|
Chris@515
|
353
|
Chris@531
|
354
|
Chris@531
|
355 void
|
Chris@531
|
356 FileFeatureWriter::finish()
|
Chris@531
|
357 {
|
Chris@690
|
358 // SVDEBUG << "FileFeatureWriter::finish()" << endl;
|
Chris@531
|
359
|
Chris@531
|
360 if (m_singleFileName != "" || m_stdout) return;
|
Chris@531
|
361
|
Chris@531
|
362 while (!m_streams.empty()) {
|
Chris@531
|
363 m_streams.begin()->second->flush();
|
Chris@531
|
364 delete m_streams.begin()->second;
|
Chris@531
|
365 m_streams.erase(m_streams.begin());
|
Chris@531
|
366 }
|
Chris@531
|
367 while (!m_files.empty()) {
|
Chris@625
|
368 if (m_files.begin()->second) {
|
Chris@690
|
369 SVDEBUG << "FileFeatureWriter::finish: NOTE: Closing feature file \""
|
Chris@686
|
370 << m_files.begin()->second->fileName() << "\"" << endl;
|
Chris@625
|
371 delete m_files.begin()->second;
|
Chris@625
|
372 }
|
Chris@531
|
373 m_files.erase(m_files.begin());
|
Chris@531
|
374 }
|
Chris@531
|
375 m_prevstream = 0;
|
Chris@531
|
376 }
|
Chris@531
|
377
|