annotate transform/FileFeatureWriter.cpp @ 604:4541581067f3

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