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@498
|
101 p.name = "stdout";
|
Chris@498
|
102 p.description = "Write all transform results directly to standard output.";
|
Chris@498
|
103 p.hasArg = false;
|
Chris@498
|
104 pl.push_back(p);
|
Chris@498
|
105 }
|
Chris@498
|
106
|
Chris@498
|
107 p.name = "force";
|
Chris@498
|
108 p.description = "If an output file already exists, overwrite it.";
|
Chris@498
|
109 p.hasArg = false;
|
Chris@498
|
110 pl.push_back(p);
|
Chris@498
|
111
|
Chris@498
|
112 p.name = "append";
|
Chris@498
|
113 p.description = "If an output file already exists, append data to it.";
|
Chris@498
|
114 p.hasArg = false;
|
Chris@498
|
115 pl.push_back(p);
|
Chris@498
|
116
|
Chris@498
|
117 return pl;
|
Chris@498
|
118 }
|
Chris@498
|
119
|
Chris@498
|
120 void
|
Chris@498
|
121 FileFeatureWriter::setParameters(map<string, string> ¶ms)
|
Chris@498
|
122 {
|
Chris@498
|
123 for (map<string, string>::iterator i = params.begin();
|
Chris@498
|
124 i != params.end(); ++i) {
|
Chris@498
|
125 if (i->first == "basedir") {
|
Chris@498
|
126 m_baseDir = i->second.c_str();
|
Chris@498
|
127 } else if (i->first == "many-files") {
|
Chris@498
|
128 if (m_support & SupportOneFilePerTrackTransform &&
|
Chris@498
|
129 m_support & SupportOneFilePerTrack) {
|
Chris@498
|
130 if (m_singleFileName != "") {
|
Chris@690
|
131 SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both one-file and many-files parameters provided, ignoring many-files" << endl;
|
Chris@498
|
132 } else {
|
Chris@498
|
133 m_manyFiles = true;
|
Chris@498
|
134 }
|
Chris@498
|
135 }
|
Chris@498
|
136 } else if (i->first == "one-file") {
|
Chris@498
|
137 if (m_support & SupportOneFileTotal) {
|
Chris@498
|
138 if (m_support & ~SupportOneFileTotal) { // not only option
|
Chris@659
|
139 // No, we cannot do this test because m_manyFiles
|
Chris@659
|
140 // may be on by default (for any FileFeatureWriter
|
Chris@659
|
141 // that supports OneFilePerTrackTransform but not
|
Chris@659
|
142 // OneFilePerTrack), so we need to be able to
|
Chris@659
|
143 // override it
|
Chris@659
|
144 // if (m_manyFiles) {
|
Chris@690
|
145 // SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both many-files and one-file parameters provided, ignoring one-file" << endl;
|
Chris@659
|
146 // } else {
|
Chris@498
|
147 m_singleFileName = i->second.c_str();
|
Chris@659
|
148 // }
|
Chris@498
|
149 }
|
Chris@498
|
150 }
|
Chris@498
|
151 } else if (i->first == "stdout") {
|
Chris@498
|
152 if (m_support & SupportOneFileTotal) {
|
Chris@498
|
153 if (m_singleFileName != "") {
|
Chris@690
|
154 SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both stdout and one-file provided, ignoring stdout" << endl;
|
Chris@498
|
155 } else {
|
Chris@498
|
156 m_stdout = true;
|
Chris@498
|
157 }
|
Chris@498
|
158 }
|
Chris@498
|
159 } else if (i->first == "append") {
|
Chris@498
|
160 m_append = true;
|
Chris@498
|
161 } else if (i->first == "force") {
|
Chris@498
|
162 m_force = true;
|
Chris@498
|
163 }
|
Chris@498
|
164 }
|
Chris@498
|
165 }
|
Chris@498
|
166
|
Chris@625
|
167 QString
|
Chris@625
|
168 FileFeatureWriter::getOutputFilename(QString trackId,
|
Chris@625
|
169 TransformId transformId)
|
Chris@498
|
170 {
|
Chris@498
|
171 if (m_singleFileName != "") {
|
Chris@498
|
172 if (QFileInfo(m_singleFileName).exists() && !(m_force || m_append)) {
|
Chris@686
|
173 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
|
174 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
|
175 return "";
|
Chris@498
|
176 }
|
Chris@498
|
177 return m_singleFileName;
|
Chris@498
|
178 }
|
Chris@498
|
179
|
Chris@498
|
180 if (m_stdout) return "";
|
Chris@498
|
181
|
Christophe@615
|
182 QUrl url(trackId, QUrl::StrictMode);
|
Chris@498
|
183 QString scheme = url.scheme().toLower();
|
Chris@498
|
184 bool local = (scheme == "" || scheme == "file" || scheme.length() == 1);
|
Chris@498
|
185
|
Chris@498
|
186 QString dirname, basename;
|
Chris@498
|
187 QString infilename = url.toLocalFile();
|
Chris@519
|
188 if (infilename == "") {
|
Chris@519
|
189 infilename = url.path();
|
Chris@519
|
190 }
|
Christophe@615
|
191 basename = QFileInfo(infilename).completeBaseName();
|
Chris@519
|
192 if (scheme.length() == 1) {
|
Chris@519
|
193 infilename = scheme + ":" + infilename; // DOS drive!
|
Chris@519
|
194 }
|
Chris@507
|
195
|
Chris@686
|
196 // cerr << "trackId = " << trackId << ", url = " << url.toString() << ", infilename = "
|
Chris@686
|
197 // << infilename << ", basename = " << basename << ", m_baseDir = " << m_baseDir << endl;
|
Chris@498
|
198
|
Chris@498
|
199 if (m_baseDir != "") dirname = QFileInfo(m_baseDir).absoluteFilePath();
|
Chris@498
|
200 else if (local) dirname = QFileInfo(infilename).absolutePath();
|
Chris@498
|
201 else dirname = QDir::currentPath();
|
Chris@498
|
202
|
Chris@686
|
203 // cerr << "dirname = " << dirname << endl;
|
Chris@604
|
204
|
Chris@498
|
205 QString filename;
|
Chris@498
|
206
|
Chris@498
|
207 if (m_manyFiles && transformId != "") {
|
Chris@514
|
208 filename = QString("%1_%2.%3").arg(basename).arg(transformId).arg(m_extension);
|
Chris@498
|
209 } else {
|
Chris@498
|
210 filename = QString("%1.%2").arg(basename).arg(m_extension);
|
Chris@498
|
211 }
|
Chris@498
|
212
|
Chris@514
|
213 filename.replace(':', '_'); // ':' not permitted in Windows
|
Chris@514
|
214
|
Chris@498
|
215 filename = QDir(dirname).filePath(filename);
|
Chris@498
|
216
|
Chris@498
|
217 if (QFileInfo(filename).exists() && !(m_force || m_append)) {
|
Chris@686
|
218 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
|
219 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
|
220 return "";
|
Chris@498
|
221 }
|
Chris@498
|
222
|
Chris@498
|
223 return filename;
|
Chris@498
|
224 }
|
Chris@498
|
225
|
Chris@625
|
226 void
|
Chris@625
|
227 FileFeatureWriter::testOutputFile(QString trackId,
|
Chris@625
|
228 TransformId transformId)
|
Chris@625
|
229 {
|
Chris@626
|
230 // Obviously, if we're writing to stdout we can't test for an
|
Chris@626
|
231 // openable output file. But when writing a single file we don't
|
Chris@626
|
232 // want to either, because this test would fail on the second and
|
Chris@626
|
233 // subsequent input files (because the file would already exist).
|
Chris@626
|
234 // getOutputFile does the right thing in this case, so we just
|
Chris@626
|
235 // leave it to it
|
Chris@626
|
236 if (m_stdout || m_singleFileName != "") return;
|
Chris@626
|
237
|
Chris@625
|
238 QString filename = getOutputFilename(trackId, transformId);
|
Chris@625
|
239 if (filename == "") {
|
Chris@625
|
240 throw FailedToOpenOutputStream(trackId, transformId);
|
Chris@625
|
241 }
|
Chris@625
|
242 }
|
Chris@498
|
243
|
Chris@625
|
244 QFile *
|
Chris@625
|
245 FileFeatureWriter::getOutputFile(QString trackId,
|
Chris@625
|
246 TransformId transformId)
|
Chris@498
|
247 {
|
Chris@498
|
248 pair<QString, TransformId> key;
|
Chris@498
|
249
|
Chris@498
|
250 if (m_singleFileName != "") {
|
Chris@498
|
251 key = pair<QString, TransformId>("", "");
|
Chris@498
|
252 } else if (m_manyFiles) {
|
Chris@498
|
253 key = pair<QString, TransformId>(trackId, transformId);
|
Chris@498
|
254 } else {
|
Chris@498
|
255 key = pair<QString, TransformId>(trackId, "");
|
Chris@498
|
256 }
|
Chris@498
|
257
|
Chris@498
|
258 if (m_files.find(key) == m_files.end()) {
|
Chris@498
|
259
|
Chris@498
|
260 QString filename = getOutputFilename(trackId, transformId);
|
Chris@498
|
261
|
Chris@625
|
262 if (filename == "") { // stdout or failure
|
Chris@498
|
263 return 0;
|
Chris@498
|
264 }
|
Chris@498
|
265
|
Chris@690
|
266 SVDEBUG << "FileFeatureWriter: NOTE: Using output filename \""
|
Chris@686
|
267 << filename << "\"" << endl;
|
Chris@498
|
268
|
Chris@591
|
269 if (m_append) {
|
Chris@690
|
270 SVDEBUG << "FileFeatureWriter: NOTE: Calling reviewFileForAppending" << endl;
|
Chris@591
|
271 reviewFileForAppending(filename);
|
Chris@591
|
272 }
|
Chris@591
|
273
|
Chris@498
|
274 QFile *file = new QFile(filename);
|
Chris@498
|
275 QIODevice::OpenMode mode = (QIODevice::WriteOnly);
|
Chris@498
|
276 if (m_append) mode |= QIODevice::Append;
|
Chris@498
|
277
|
Chris@498
|
278 if (!file->open(mode)) {
|
Chris@498
|
279 cerr << "FileFeatureWriter: ERROR: Failed to open output file \"" << filename.toStdString()
|
Chris@498
|
280 << "\" for writing" << endl;
|
Chris@498
|
281 delete file;
|
Chris@498
|
282 m_files[key] = 0;
|
Chris@498
|
283 throw FailedToOpenFile(filename);
|
Chris@498
|
284 }
|
Chris@626
|
285
|
Chris@498
|
286 m_files[key] = file;
|
Chris@498
|
287 }
|
Chris@498
|
288
|
Chris@498
|
289 return m_files[key];
|
Chris@498
|
290 }
|
Chris@498
|
291
|
Chris@498
|
292
|
Chris@498
|
293 QTextStream *FileFeatureWriter::getOutputStream(QString trackId,
|
Chris@498
|
294 TransformId transformId)
|
Chris@498
|
295 {
|
Chris@498
|
296 QFile *file = getOutputFile(trackId, transformId);
|
Chris@498
|
297 if (!file && !m_stdout) {
|
Chris@498
|
298 return 0;
|
Chris@498
|
299 }
|
Chris@626
|
300
|
Chris@498
|
301 if (m_streams.find(file) == m_streams.end()) {
|
Chris@498
|
302 if (m_stdout) {
|
Chris@498
|
303 m_streams[file] = new QTextStream(stdout);
|
Chris@498
|
304 } else {
|
Chris@498
|
305 m_streams[file] = new QTextStream(file);
|
Chris@498
|
306 }
|
Chris@498
|
307 }
|
Chris@498
|
308
|
Chris@512
|
309 QTextStream *stream = m_streams[file];
|
Chris@512
|
310
|
Chris@512
|
311 if (m_prevstream && stream != m_prevstream) {
|
Chris@512
|
312 m_prevstream->flush();
|
Chris@512
|
313 }
|
Chris@512
|
314 m_prevstream = stream;
|
Chris@512
|
315
|
Chris@512
|
316 return stream;
|
Chris@498
|
317 }
|
Chris@498
|
318
|
Chris@515
|
319
|
Chris@515
|
320 void
|
Chris@515
|
321 FileFeatureWriter::flush()
|
Chris@515
|
322 {
|
Chris@515
|
323 if (m_prevstream) {
|
Chris@515
|
324 m_prevstream->flush();
|
Chris@515
|
325 }
|
Chris@515
|
326 }
|
Chris@515
|
327
|
Chris@531
|
328
|
Chris@531
|
329 void
|
Chris@531
|
330 FileFeatureWriter::finish()
|
Chris@531
|
331 {
|
Chris@690
|
332 // SVDEBUG << "FileFeatureWriter::finish()" << endl;
|
Chris@531
|
333
|
Chris@531
|
334 if (m_singleFileName != "" || m_stdout) return;
|
Chris@531
|
335
|
Chris@531
|
336 while (!m_streams.empty()) {
|
Chris@531
|
337 m_streams.begin()->second->flush();
|
Chris@531
|
338 delete m_streams.begin()->second;
|
Chris@531
|
339 m_streams.erase(m_streams.begin());
|
Chris@531
|
340 }
|
Chris@531
|
341 while (!m_files.empty()) {
|
Chris@625
|
342 if (m_files.begin()->second) {
|
Chris@690
|
343 SVDEBUG << "FileFeatureWriter::finish: NOTE: Closing feature file \""
|
Chris@686
|
344 << m_files.begin()->second->fileName() << "\"" << endl;
|
Chris@625
|
345 delete m_files.begin()->second;
|
Chris@625
|
346 }
|
Chris@531
|
347 m_files.erase(m_files.begin());
|
Chris@531
|
348 }
|
Chris@531
|
349 m_prevstream = 0;
|
Chris@531
|
350 }
|
Chris@531
|
351
|