annotate transform/FileFeatureWriter.cpp @ 625:608b4dc5ff34

* Improve behaviour when output file write fails (i.e. fail earlier)
author Chris Cannam
date Fri, 21 May 2010 10:21:18 +0000
parents 521438145bd7
children 686fc1703a33
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@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> &params)
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