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@625
|
221 if (m_stdout) return;
|
Chris@625
|
222 QString filename = getOutputFilename(trackId, transformId);
|
Chris@625
|
223 if (filename == "") {
|
Chris@625
|
224 throw FailedToOpenOutputStream(trackId, transformId);
|
Chris@625
|
225 }
|
Chris@625
|
226 }
|
Chris@498
|
227
|
Chris@625
|
228 QFile *
|
Chris@625
|
229 FileFeatureWriter::getOutputFile(QString trackId,
|
Chris@625
|
230 TransformId transformId)
|
Chris@498
|
231 {
|
Chris@498
|
232 pair<QString, TransformId> key;
|
Chris@498
|
233
|
Chris@498
|
234 if (m_singleFileName != "") {
|
Chris@498
|
235 key = pair<QString, TransformId>("", "");
|
Chris@498
|
236 } else if (m_manyFiles) {
|
Chris@498
|
237 key = pair<QString, TransformId>(trackId, transformId);
|
Chris@498
|
238 } else {
|
Chris@498
|
239 key = pair<QString, TransformId>(trackId, "");
|
Chris@498
|
240 }
|
Chris@498
|
241
|
Chris@498
|
242 if (m_files.find(key) == m_files.end()) {
|
Chris@498
|
243
|
Chris@498
|
244 QString filename = getOutputFilename(trackId, transformId);
|
Chris@498
|
245
|
Chris@625
|
246 if (filename == "") { // stdout or failure
|
Chris@498
|
247 return 0;
|
Chris@498
|
248 }
|
Chris@498
|
249
|
Chris@498
|
250 cerr << "FileFeatureWriter: NOTE: Using output filename \""
|
Chris@498
|
251 << filename.toStdString() << "\"" << endl;
|
Chris@498
|
252
|
Chris@591
|
253 if (m_append) {
|
Chris@591
|
254 cerr << "FileFeatureWriter: NOTE: Calling reviewFileForAppending" << endl;
|
Chris@591
|
255 reviewFileForAppending(filename);
|
Chris@591
|
256 }
|
Chris@591
|
257
|
Chris@498
|
258 QFile *file = new QFile(filename);
|
Chris@498
|
259 QIODevice::OpenMode mode = (QIODevice::WriteOnly);
|
Chris@498
|
260 if (m_append) mode |= QIODevice::Append;
|
Chris@498
|
261
|
Chris@498
|
262 if (!file->open(mode)) {
|
Chris@498
|
263 cerr << "FileFeatureWriter: ERROR: Failed to open output file \"" << filename.toStdString()
|
Chris@498
|
264 << "\" for writing" << endl;
|
Chris@498
|
265 delete file;
|
Chris@498
|
266 m_files[key] = 0;
|
Chris@498
|
267 throw FailedToOpenFile(filename);
|
Chris@498
|
268 }
|
Chris@498
|
269
|
Chris@498
|
270 m_files[key] = file;
|
Chris@498
|
271 }
|
Chris@498
|
272
|
Chris@498
|
273 return m_files[key];
|
Chris@498
|
274 }
|
Chris@498
|
275
|
Chris@498
|
276
|
Chris@498
|
277 QTextStream *FileFeatureWriter::getOutputStream(QString trackId,
|
Chris@498
|
278 TransformId transformId)
|
Chris@498
|
279 {
|
Chris@498
|
280 QFile *file = getOutputFile(trackId, transformId);
|
Chris@498
|
281 if (!file && !m_stdout) {
|
Chris@498
|
282 return 0;
|
Chris@498
|
283 }
|
Chris@498
|
284
|
Chris@498
|
285 if (m_streams.find(file) == m_streams.end()) {
|
Chris@498
|
286 if (m_stdout) {
|
Chris@498
|
287 m_streams[file] = new QTextStream(stdout);
|
Chris@498
|
288 } else {
|
Chris@498
|
289 m_streams[file] = new QTextStream(file);
|
Chris@498
|
290 }
|
Chris@498
|
291 }
|
Chris@498
|
292
|
Chris@512
|
293 QTextStream *stream = m_streams[file];
|
Chris@512
|
294
|
Chris@512
|
295 if (m_prevstream && stream != m_prevstream) {
|
Chris@512
|
296 m_prevstream->flush();
|
Chris@512
|
297 }
|
Chris@512
|
298 m_prevstream = stream;
|
Chris@512
|
299
|
Chris@512
|
300 return stream;
|
Chris@498
|
301 }
|
Chris@498
|
302
|
Chris@515
|
303
|
Chris@515
|
304 void
|
Chris@515
|
305 FileFeatureWriter::flush()
|
Chris@515
|
306 {
|
Chris@515
|
307 if (m_prevstream) {
|
Chris@515
|
308 m_prevstream->flush();
|
Chris@515
|
309 }
|
Chris@515
|
310 }
|
Chris@515
|
311
|
Chris@531
|
312
|
Chris@531
|
313 void
|
Chris@531
|
314 FileFeatureWriter::finish()
|
Chris@531
|
315 {
|
Chris@579
|
316 // cerr << "FileFeatureWriter::finish()" << endl;
|
Chris@531
|
317
|
Chris@531
|
318 if (m_singleFileName != "" || m_stdout) return;
|
Chris@531
|
319
|
Chris@531
|
320 while (!m_streams.empty()) {
|
Chris@531
|
321 m_streams.begin()->second->flush();
|
Chris@531
|
322 delete m_streams.begin()->second;
|
Chris@531
|
323 m_streams.erase(m_streams.begin());
|
Chris@531
|
324 }
|
Chris@531
|
325 while (!m_files.empty()) {
|
Chris@625
|
326 if (m_files.begin()->second) {
|
Chris@625
|
327 cerr << "FileFeatureWriter::finish: NOTE: Closing feature file \""
|
Chris@625
|
328 << m_files.begin()->second->fileName().toStdString() << "\"" << endl;
|
Chris@625
|
329 delete m_files.begin()->second;
|
Chris@625
|
330 }
|
Chris@531
|
331 m_files.erase(m_files.begin());
|
Chris@531
|
332 }
|
Chris@531
|
333 m_prevstream = 0;
|
Chris@531
|
334 }
|
Chris@531
|
335
|