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@498
|
78 p.description = "Base output directory path. (The default is the same directory as the input file.)";
|
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@498
|
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@659
|
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@659
|
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@625
|
170 FileFeatureWriter::getOutputFilename(QString trackId,
|
Chris@625
|
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@498
|
182 if (m_stdout) return "";
|
Chris@498
|
183
|
Christophe@615
|
184 QUrl url(trackId, QUrl::StrictMode);
|
Chris@498
|
185 QString scheme = url.scheme().toLower();
|
Chris@498
|
186 bool local = (scheme == "" || scheme == "file" || scheme.length() == 1);
|
Chris@498
|
187
|
Chris@498
|
188 QString dirname, basename;
|
Chris@498
|
189 QString infilename = url.toLocalFile();
|
Chris@519
|
190 if (infilename == "") {
|
Chris@519
|
191 infilename = url.path();
|
Chris@519
|
192 }
|
Christophe@615
|
193 basename = QFileInfo(infilename).completeBaseName();
|
Chris@519
|
194 if (scheme.length() == 1) {
|
Chris@519
|
195 infilename = scheme + ":" + infilename; // DOS drive!
|
Chris@519
|
196 }
|
Chris@507
|
197
|
Chris@686
|
198 // cerr << "trackId = " << trackId << ", url = " << url.toString() << ", infilename = "
|
Chris@686
|
199 // << infilename << ", basename = " << basename << ", m_baseDir = " << m_baseDir << endl;
|
Chris@498
|
200
|
Chris@498
|
201 if (m_baseDir != "") dirname = QFileInfo(m_baseDir).absoluteFilePath();
|
Chris@498
|
202 else if (local) dirname = QFileInfo(infilename).absolutePath();
|
Chris@498
|
203 else dirname = QDir::currentPath();
|
Chris@498
|
204
|
Chris@686
|
205 // cerr << "dirname = " << dirname << endl;
|
Chris@604
|
206
|
Chris@498
|
207 QString filename;
|
Chris@498
|
208
|
Chris@498
|
209 if (m_manyFiles && transformId != "") {
|
Chris@514
|
210 filename = QString("%1_%2.%3").arg(basename).arg(transformId).arg(m_extension);
|
Chris@498
|
211 } else {
|
Chris@498
|
212 filename = QString("%1.%2").arg(basename).arg(m_extension);
|
Chris@498
|
213 }
|
Chris@498
|
214
|
Chris@514
|
215 filename.replace(':', '_'); // ':' not permitted in Windows
|
Chris@514
|
216
|
Chris@498
|
217 filename = QDir(dirname).filePath(filename);
|
Chris@498
|
218
|
Chris@498
|
219 if (QFileInfo(filename).exists() && !(m_force || m_append)) {
|
Chris@686
|
220 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
|
221 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
|
222 return "";
|
Chris@498
|
223 }
|
Chris@498
|
224
|
Chris@498
|
225 return filename;
|
Chris@498
|
226 }
|
Chris@498
|
227
|
Chris@625
|
228 void
|
Chris@625
|
229 FileFeatureWriter::testOutputFile(QString trackId,
|
Chris@625
|
230 TransformId transformId)
|
Chris@625
|
231 {
|
Chris@626
|
232 // Obviously, if we're writing to stdout we can't test for an
|
Chris@626
|
233 // openable output file. But when writing a single file we don't
|
Chris@626
|
234 // want to either, because this test would fail on the second and
|
Chris@626
|
235 // subsequent input files (because the file would already exist).
|
Chris@626
|
236 // getOutputFile does the right thing in this case, so we just
|
Chris@626
|
237 // leave it to it
|
Chris@626
|
238 if (m_stdout || m_singleFileName != "") return;
|
Chris@626
|
239
|
Chris@625
|
240 QString filename = getOutputFilename(trackId, transformId);
|
Chris@625
|
241 if (filename == "") {
|
Chris@625
|
242 throw FailedToOpenOutputStream(trackId, transformId);
|
Chris@625
|
243 }
|
Chris@625
|
244 }
|
Chris@498
|
245
|
Chris@625
|
246 QFile *
|
Chris@625
|
247 FileFeatureWriter::getOutputFile(QString trackId,
|
Chris@625
|
248 TransformId transformId)
|
Chris@498
|
249 {
|
Chris@498
|
250 pair<QString, TransformId> key;
|
Chris@498
|
251
|
Chris@498
|
252 if (m_singleFileName != "") {
|
Chris@498
|
253 key = pair<QString, TransformId>("", "");
|
Chris@498
|
254 } else if (m_manyFiles) {
|
Chris@498
|
255 key = pair<QString, TransformId>(trackId, transformId);
|
Chris@498
|
256 } else {
|
Chris@498
|
257 key = pair<QString, TransformId>(trackId, "");
|
Chris@498
|
258 }
|
Chris@498
|
259
|
Chris@498
|
260 if (m_files.find(key) == m_files.end()) {
|
Chris@498
|
261
|
Chris@498
|
262 QString filename = getOutputFilename(trackId, transformId);
|
Chris@498
|
263
|
Chris@625
|
264 if (filename == "") { // stdout or failure
|
Chris@498
|
265 return 0;
|
Chris@498
|
266 }
|
Chris@498
|
267
|
Chris@690
|
268 SVDEBUG << "FileFeatureWriter: NOTE: Using output filename \""
|
Chris@686
|
269 << filename << "\"" << endl;
|
Chris@498
|
270
|
Chris@591
|
271 if (m_append) {
|
Chris@690
|
272 SVDEBUG << "FileFeatureWriter: NOTE: Calling reviewFileForAppending" << endl;
|
Chris@591
|
273 reviewFileForAppending(filename);
|
Chris@591
|
274 }
|
Chris@591
|
275
|
Chris@498
|
276 QFile *file = new QFile(filename);
|
Chris@498
|
277 QIODevice::OpenMode mode = (QIODevice::WriteOnly);
|
Chris@498
|
278 if (m_append) mode |= QIODevice::Append;
|
Chris@498
|
279
|
Chris@498
|
280 if (!file->open(mode)) {
|
Chris@844
|
281 cerr << "FileFeatureWriter: ERROR: Failed to open output file \"" << filename
|
Chris@498
|
282 << "\" for writing" << endl;
|
Chris@498
|
283 delete file;
|
Chris@498
|
284 m_files[key] = 0;
|
Chris@498
|
285 throw FailedToOpenFile(filename);
|
Chris@498
|
286 }
|
Chris@626
|
287
|
Chris@498
|
288 m_files[key] = file;
|
Chris@498
|
289 }
|
Chris@498
|
290
|
Chris@498
|
291 return m_files[key];
|
Chris@498
|
292 }
|
Chris@498
|
293
|
Chris@498
|
294
|
Chris@498
|
295 QTextStream *FileFeatureWriter::getOutputStream(QString trackId,
|
Chris@498
|
296 TransformId transformId)
|
Chris@498
|
297 {
|
Chris@498
|
298 QFile *file = getOutputFile(trackId, transformId);
|
Chris@498
|
299 if (!file && !m_stdout) {
|
Chris@498
|
300 return 0;
|
Chris@498
|
301 }
|
Chris@626
|
302
|
Chris@498
|
303 if (m_streams.find(file) == m_streams.end()) {
|
Chris@498
|
304 if (m_stdout) {
|
Chris@498
|
305 m_streams[file] = new QTextStream(stdout);
|
Chris@498
|
306 } else {
|
Chris@498
|
307 m_streams[file] = new QTextStream(file);
|
Chris@498
|
308 }
|
Chris@498
|
309 }
|
Chris@498
|
310
|
Chris@512
|
311 QTextStream *stream = m_streams[file];
|
Chris@512
|
312
|
Chris@512
|
313 if (m_prevstream && stream != m_prevstream) {
|
Chris@512
|
314 m_prevstream->flush();
|
Chris@512
|
315 }
|
Chris@512
|
316 m_prevstream = stream;
|
Chris@512
|
317
|
Chris@512
|
318 return stream;
|
Chris@498
|
319 }
|
Chris@498
|
320
|
Chris@515
|
321
|
Chris@515
|
322 void
|
Chris@515
|
323 FileFeatureWriter::flush()
|
Chris@515
|
324 {
|
Chris@515
|
325 if (m_prevstream) {
|
Chris@515
|
326 m_prevstream->flush();
|
Chris@515
|
327 }
|
Chris@515
|
328 }
|
Chris@515
|
329
|
Chris@531
|
330
|
Chris@531
|
331 void
|
Chris@531
|
332 FileFeatureWriter::finish()
|
Chris@531
|
333 {
|
Chris@690
|
334 // SVDEBUG << "FileFeatureWriter::finish()" << endl;
|
Chris@531
|
335
|
Chris@531
|
336 if (m_singleFileName != "" || m_stdout) return;
|
Chris@531
|
337
|
Chris@531
|
338 while (!m_streams.empty()) {
|
Chris@531
|
339 m_streams.begin()->second->flush();
|
Chris@531
|
340 delete m_streams.begin()->second;
|
Chris@531
|
341 m_streams.erase(m_streams.begin());
|
Chris@531
|
342 }
|
Chris@531
|
343 while (!m_files.empty()) {
|
Chris@625
|
344 if (m_files.begin()->second) {
|
Chris@690
|
345 SVDEBUG << "FileFeatureWriter::finish: NOTE: Closing feature file \""
|
Chris@686
|
346 << m_files.begin()->second->fileName() << "\"" << endl;
|
Chris@625
|
347 delete m_files.begin()->second;
|
Chris@625
|
348 }
|
Chris@531
|
349 m_files.erase(m_files.begin());
|
Chris@531
|
350 }
|
Chris@531
|
351 m_prevstream = 0;
|
Chris@531
|
352 }
|
Chris@531
|
353
|