annotate transform/FileFeatureWriter.cpp @ 626:686fc1703a33

* Avoid failing on second and subsequent input files in single-output-file mode (because first input file caused output file to be open, so second input file failed because output "already existed")
author Chris Cannam
date Mon, 24 May 2010 16:04:10 +0000
parents 608b4dc5ff34
children cd1fa6387cb9
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@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