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@498
|
62 delete m_files.begin()->second;
|
Chris@498
|
63 m_files.erase(m_files.begin());
|
Chris@498
|
64 }
|
Chris@498
|
65 }
|
Chris@498
|
66
|
Chris@498
|
67 FileFeatureWriter::ParameterList
|
Chris@498
|
68 FileFeatureWriter::getSupportedParameters() const
|
Chris@498
|
69 {
|
Chris@498
|
70 ParameterList pl;
|
Chris@498
|
71 Parameter p;
|
Chris@498
|
72
|
Chris@498
|
73 p.name = "basedir";
|
Chris@498
|
74 p.description = "Base output directory path. (The default is the same directory as the input file.)";
|
Chris@498
|
75 p.hasArg = true;
|
Chris@498
|
76 pl.push_back(p);
|
Chris@498
|
77
|
Chris@498
|
78 if (m_support & SupportOneFilePerTrackTransform &&
|
Chris@498
|
79 m_support & SupportOneFilePerTrack) {
|
Chris@498
|
80 p.name = "many-files";
|
Chris@498
|
81 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
|
82 p.hasArg = false;
|
Chris@498
|
83 pl.push_back(p);
|
Chris@498
|
84 }
|
Chris@498
|
85
|
Chris@498
|
86 if (m_support & SupportOneFileTotal) {
|
Chris@498
|
87 if (m_support & ~SupportOneFileTotal) { // not only option
|
Chris@498
|
88 p.name = "one-file";
|
Chris@498
|
89 p.description = "Write all transform results for all input files into the single named output file.";
|
Chris@498
|
90 p.hasArg = true;
|
Chris@498
|
91 pl.push_back(p);
|
Chris@498
|
92 }
|
Chris@498
|
93 p.name = "stdout";
|
Chris@498
|
94 p.description = "Write all transform results directly to standard output.";
|
Chris@498
|
95 p.hasArg = false;
|
Chris@498
|
96 pl.push_back(p);
|
Chris@498
|
97 }
|
Chris@498
|
98
|
Chris@498
|
99 p.name = "force";
|
Chris@498
|
100 p.description = "If an output file already exists, overwrite it.";
|
Chris@498
|
101 p.hasArg = false;
|
Chris@498
|
102 pl.push_back(p);
|
Chris@498
|
103
|
Chris@498
|
104 p.name = "append";
|
Chris@498
|
105 p.description = "If an output file already exists, append data to it.";
|
Chris@498
|
106 p.hasArg = false;
|
Chris@498
|
107 pl.push_back(p);
|
Chris@498
|
108
|
Chris@498
|
109 return pl;
|
Chris@498
|
110 }
|
Chris@498
|
111
|
Chris@498
|
112 void
|
Chris@498
|
113 FileFeatureWriter::setParameters(map<string, string> ¶ms)
|
Chris@498
|
114 {
|
Chris@498
|
115 for (map<string, string>::iterator i = params.begin();
|
Chris@498
|
116 i != params.end(); ++i) {
|
Chris@498
|
117 if (i->first == "basedir") {
|
Chris@498
|
118 m_baseDir = i->second.c_str();
|
Chris@498
|
119 } else if (i->first == "many-files") {
|
Chris@498
|
120 if (m_support & SupportOneFilePerTrackTransform &&
|
Chris@498
|
121 m_support & SupportOneFilePerTrack) {
|
Chris@498
|
122 if (m_singleFileName != "") {
|
Chris@498
|
123 cerr << "FileFeatureWriter::setParameters: WARNING: Both one-file and many-files parameters provided, ignoring many-files" << endl;
|
Chris@498
|
124 } else {
|
Chris@498
|
125 m_manyFiles = true;
|
Chris@498
|
126 }
|
Chris@498
|
127 }
|
Chris@498
|
128 } else if (i->first == "one-file") {
|
Chris@498
|
129 if (m_support & SupportOneFileTotal) {
|
Chris@498
|
130 if (m_support & ~SupportOneFileTotal) { // not only option
|
Chris@498
|
131 if (m_manyFiles) {
|
Chris@498
|
132 cerr << "FileFeatureWriter::setParameters: WARNING: Both many-files and one-file parameters provided, ignoring one-file" << endl;
|
Chris@498
|
133 } else {
|
Chris@498
|
134 m_singleFileName = i->second.c_str();
|
Chris@498
|
135 }
|
Chris@498
|
136 }
|
Chris@498
|
137 }
|
Chris@498
|
138 } else if (i->first == "stdout") {
|
Chris@498
|
139 if (m_support & SupportOneFileTotal) {
|
Chris@498
|
140 if (m_singleFileName != "") {
|
Chris@498
|
141 cerr << "FileFeatureWriter::setParameters: WARNING: Both stdout and one-file provided, ignoring stdout" << endl;
|
Chris@498
|
142 } else {
|
Chris@498
|
143 m_stdout = true;
|
Chris@498
|
144 }
|
Chris@498
|
145 }
|
Chris@498
|
146 } else if (i->first == "append") {
|
Chris@498
|
147 m_append = true;
|
Chris@498
|
148 } else if (i->first == "force") {
|
Chris@498
|
149 m_force = true;
|
Chris@498
|
150 }
|
Chris@498
|
151 }
|
Chris@498
|
152 }
|
Chris@498
|
153
|
Chris@498
|
154 QString FileFeatureWriter::getOutputFilename(QString trackId,
|
Chris@498
|
155 TransformId transformId)
|
Chris@498
|
156 {
|
Chris@498
|
157 if (m_singleFileName != "") {
|
Chris@498
|
158 if (QFileInfo(m_singleFileName).exists() && !(m_force || m_append)) {
|
Chris@498
|
159 cerr << "FileFeatureWriter: ERROR: Specified output file \"" << m_singleFileName.toStdString() << "\" exists and neither force nor append flag is specified -- not overwriting" << endl;
|
Chris@498
|
160 return "";
|
Chris@498
|
161 }
|
Chris@498
|
162 return m_singleFileName;
|
Chris@498
|
163 }
|
Chris@498
|
164
|
Chris@498
|
165 if (m_stdout) return "";
|
Chris@498
|
166
|
Chris@498
|
167 QUrl url(trackId);
|
Chris@498
|
168 QString scheme = url.scheme().toLower();
|
Chris@498
|
169 bool local = (scheme == "" || scheme == "file" || scheme.length() == 1);
|
Chris@498
|
170
|
Chris@498
|
171 QString dirname, basename;
|
Chris@498
|
172 QString infilename = url.toLocalFile();
|
Chris@519
|
173 if (infilename == "") {
|
Chris@519
|
174 infilename = url.path();
|
Chris@519
|
175 }
|
Chris@498
|
176 basename = QFileInfo(infilename).baseName();
|
Chris@519
|
177 if (scheme.length() == 1) {
|
Chris@519
|
178 infilename = scheme + ":" + infilename; // DOS drive!
|
Chris@519
|
179 }
|
Chris@507
|
180
|
Chris@524
|
181 // cerr << "trackId = " << trackId.toStdString() << ", url = " << url.toString().toStdString() << ", infilename = "
|
Chris@524
|
182 // << infilename.toStdString() << ", basename = " << basename.toStdString() << endl;
|
Chris@498
|
183
|
Chris@498
|
184
|
Chris@498
|
185 if (m_baseDir != "") dirname = QFileInfo(m_baseDir).absoluteFilePath();
|
Chris@498
|
186 else if (local) dirname = QFileInfo(infilename).absolutePath();
|
Chris@498
|
187 else dirname = QDir::currentPath();
|
Chris@498
|
188
|
Chris@498
|
189 QString filename;
|
Chris@498
|
190
|
Chris@498
|
191 if (m_manyFiles && transformId != "") {
|
Chris@514
|
192 filename = QString("%1_%2.%3").arg(basename).arg(transformId).arg(m_extension);
|
Chris@498
|
193 } else {
|
Chris@498
|
194 filename = QString("%1.%2").arg(basename).arg(m_extension);
|
Chris@498
|
195 }
|
Chris@498
|
196
|
Chris@514
|
197 filename.replace(':', '_'); // ':' not permitted in Windows
|
Chris@514
|
198
|
Chris@498
|
199 filename = QDir(dirname).filePath(filename);
|
Chris@498
|
200
|
Chris@498
|
201 if (QFileInfo(filename).exists() && !(m_force || m_append)) {
|
Chris@498
|
202 cerr << "FileFeatureWriter: ERROR: Output file \"" << filename.toStdString() << "\" exists (for input file or URL \"" << trackId.toStdString() << "\" and transform \"" << transformId.toStdString() << "\") and neither force nor append is specified -- not overwriting" << endl;
|
Chris@498
|
203 return "";
|
Chris@498
|
204 }
|
Chris@498
|
205
|
Chris@498
|
206 return filename;
|
Chris@498
|
207 }
|
Chris@498
|
208
|
Chris@498
|
209
|
Chris@498
|
210 QFile *FileFeatureWriter::getOutputFile(QString trackId,
|
Chris@498
|
211 TransformId transformId)
|
Chris@498
|
212 {
|
Chris@498
|
213 pair<QString, TransformId> key;
|
Chris@498
|
214
|
Chris@498
|
215 if (m_singleFileName != "") {
|
Chris@498
|
216 key = pair<QString, TransformId>("", "");
|
Chris@498
|
217 } else if (m_manyFiles) {
|
Chris@498
|
218 key = pair<QString, TransformId>(trackId, transformId);
|
Chris@498
|
219 } else {
|
Chris@498
|
220 key = pair<QString, TransformId>(trackId, "");
|
Chris@498
|
221 }
|
Chris@498
|
222
|
Chris@498
|
223 if (m_files.find(key) == m_files.end()) {
|
Chris@498
|
224
|
Chris@498
|
225 QString filename = getOutputFilename(trackId, transformId);
|
Chris@498
|
226
|
Chris@498
|
227 if (filename == "") { // stdout
|
Chris@498
|
228 return 0;
|
Chris@498
|
229 }
|
Chris@498
|
230
|
Chris@498
|
231 cerr << "FileFeatureWriter: NOTE: Using output filename \""
|
Chris@498
|
232 << filename.toStdString() << "\"" << endl;
|
Chris@498
|
233
|
Chris@591
|
234 if (m_append) {
|
Chris@591
|
235 cerr << "FileFeatureWriter: NOTE: Calling reviewFileForAppending" << endl;
|
Chris@591
|
236 reviewFileForAppending(filename);
|
Chris@591
|
237 }
|
Chris@591
|
238
|
Chris@498
|
239 QFile *file = new QFile(filename);
|
Chris@498
|
240 QIODevice::OpenMode mode = (QIODevice::WriteOnly);
|
Chris@498
|
241 if (m_append) mode |= QIODevice::Append;
|
Chris@498
|
242
|
Chris@498
|
243 if (!file->open(mode)) {
|
Chris@498
|
244 cerr << "FileFeatureWriter: ERROR: Failed to open output file \"" << filename.toStdString()
|
Chris@498
|
245 << "\" for writing" << endl;
|
Chris@498
|
246 delete file;
|
Chris@498
|
247 m_files[key] = 0;
|
Chris@498
|
248 throw FailedToOpenFile(filename);
|
Chris@498
|
249 }
|
Chris@498
|
250
|
Chris@498
|
251 m_files[key] = file;
|
Chris@498
|
252 }
|
Chris@498
|
253
|
Chris@498
|
254 return m_files[key];
|
Chris@498
|
255 }
|
Chris@498
|
256
|
Chris@498
|
257
|
Chris@498
|
258 QTextStream *FileFeatureWriter::getOutputStream(QString trackId,
|
Chris@498
|
259 TransformId transformId)
|
Chris@498
|
260 {
|
Chris@498
|
261 QFile *file = getOutputFile(trackId, transformId);
|
Chris@498
|
262 if (!file && !m_stdout) {
|
Chris@498
|
263 return 0;
|
Chris@498
|
264 }
|
Chris@498
|
265
|
Chris@498
|
266 if (m_streams.find(file) == m_streams.end()) {
|
Chris@498
|
267 if (m_stdout) {
|
Chris@498
|
268 m_streams[file] = new QTextStream(stdout);
|
Chris@498
|
269 } else {
|
Chris@498
|
270 m_streams[file] = new QTextStream(file);
|
Chris@498
|
271 }
|
Chris@498
|
272 }
|
Chris@498
|
273
|
Chris@512
|
274 QTextStream *stream = m_streams[file];
|
Chris@512
|
275
|
Chris@512
|
276 if (m_prevstream && stream != m_prevstream) {
|
Chris@512
|
277 m_prevstream->flush();
|
Chris@512
|
278 }
|
Chris@512
|
279 m_prevstream = stream;
|
Chris@512
|
280
|
Chris@512
|
281 return stream;
|
Chris@498
|
282 }
|
Chris@498
|
283
|
Chris@515
|
284
|
Chris@515
|
285 void
|
Chris@515
|
286 FileFeatureWriter::flush()
|
Chris@515
|
287 {
|
Chris@515
|
288 if (m_prevstream) {
|
Chris@515
|
289 m_prevstream->flush();
|
Chris@515
|
290 }
|
Chris@515
|
291 }
|
Chris@515
|
292
|
Chris@531
|
293
|
Chris@531
|
294 void
|
Chris@531
|
295 FileFeatureWriter::finish()
|
Chris@531
|
296 {
|
Chris@579
|
297 // cerr << "FileFeatureWriter::finish()" << endl;
|
Chris@531
|
298
|
Chris@531
|
299 if (m_singleFileName != "" || m_stdout) return;
|
Chris@531
|
300
|
Chris@531
|
301 while (!m_streams.empty()) {
|
Chris@531
|
302 m_streams.begin()->second->flush();
|
Chris@531
|
303 delete m_streams.begin()->second;
|
Chris@531
|
304 m_streams.erase(m_streams.begin());
|
Chris@531
|
305 }
|
Chris@531
|
306 while (!m_files.empty()) {
|
Chris@531
|
307 delete m_files.begin()->second;
|
Chris@531
|
308 m_files.erase(m_files.begin());
|
Chris@531
|
309 }
|
Chris@531
|
310 m_prevstream = 0;
|
Chris@531
|
311 }
|
Chris@531
|
312
|