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