annotate transform/FileFeatureWriter.cpp @ 683:f84f147572b9

Avoid crash when generating/processing a very short file
author Chris Cannam
date Wed, 11 May 2011 11:04:02 +0100
parents cd1fa6387cb9
children b4a8d8221eaf
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@659 93 if (m_support & SupportOneFilePerTrack) {
Chris@659 94 p.description = "Write all transform results for all input files into the single named output file. (The default is to create one output file per input audio file, and write all transform results for that input into it.)";
Chris@659 95 } else {
Chris@659 96 p.description = "Write all transform results for all input files into the single named output file. (The default is to create a separate output file for each combination of input audio file and transform.)";
Chris@659 97 }
Chris@498 98 p.hasArg = true;
Chris@498 99 pl.push_back(p);
Chris@498 100 }
Chris@498 101 p.name = "stdout";
Chris@498 102 p.description = "Write all transform results directly to standard output.";
Chris@498 103 p.hasArg = false;
Chris@498 104 pl.push_back(p);
Chris@498 105 }
Chris@498 106
Chris@498 107 p.name = "force";
Chris@498 108 p.description = "If an output file already exists, overwrite it.";
Chris@498 109 p.hasArg = false;
Chris@498 110 pl.push_back(p);
Chris@498 111
Chris@498 112 p.name = "append";
Chris@498 113 p.description = "If an output file already exists, append data to it.";
Chris@498 114 p.hasArg = false;
Chris@498 115 pl.push_back(p);
Chris@498 116
Chris@498 117 return pl;
Chris@498 118 }
Chris@498 119
Chris@498 120 void
Chris@498 121 FileFeatureWriter::setParameters(map<string, string> &params)
Chris@498 122 {
Chris@498 123 for (map<string, string>::iterator i = params.begin();
Chris@498 124 i != params.end(); ++i) {
Chris@498 125 if (i->first == "basedir") {
Chris@498 126 m_baseDir = i->second.c_str();
Chris@498 127 } else if (i->first == "many-files") {
Chris@498 128 if (m_support & SupportOneFilePerTrackTransform &&
Chris@498 129 m_support & SupportOneFilePerTrack) {
Chris@498 130 if (m_singleFileName != "") {
Chris@498 131 cerr << "FileFeatureWriter::setParameters: WARNING: Both one-file and many-files parameters provided, ignoring many-files" << endl;
Chris@498 132 } else {
Chris@498 133 m_manyFiles = true;
Chris@498 134 }
Chris@498 135 }
Chris@498 136 } else if (i->first == "one-file") {
Chris@498 137 if (m_support & SupportOneFileTotal) {
Chris@498 138 if (m_support & ~SupportOneFileTotal) { // not only option
Chris@659 139 // No, we cannot do this test because m_manyFiles
Chris@659 140 // may be on by default (for any FileFeatureWriter
Chris@659 141 // that supports OneFilePerTrackTransform but not
Chris@659 142 // OneFilePerTrack), so we need to be able to
Chris@659 143 // override it
Chris@659 144 // if (m_manyFiles) {
Chris@659 145 // cerr << "FileFeatureWriter::setParameters: WARNING: Both many-files and one-file parameters provided, ignoring one-file" << endl;
Chris@659 146 // } else {
Chris@498 147 m_singleFileName = i->second.c_str();
Chris@659 148 // }
Chris@498 149 }
Chris@498 150 }
Chris@498 151 } else if (i->first == "stdout") {
Chris@498 152 if (m_support & SupportOneFileTotal) {
Chris@498 153 if (m_singleFileName != "") {
Chris@498 154 cerr << "FileFeatureWriter::setParameters: WARNING: Both stdout and one-file provided, ignoring stdout" << endl;
Chris@498 155 } else {
Chris@498 156 m_stdout = true;
Chris@498 157 }
Chris@498 158 }
Chris@498 159 } else if (i->first == "append") {
Chris@498 160 m_append = true;
Chris@498 161 } else if (i->first == "force") {
Chris@498 162 m_force = true;
Chris@498 163 }
Chris@498 164 }
Chris@498 165 }
Chris@498 166
Chris@625 167 QString
Chris@625 168 FileFeatureWriter::getOutputFilename(QString trackId,
Chris@625 169 TransformId transformId)
Chris@498 170 {
Chris@498 171 if (m_singleFileName != "") {
Chris@498 172 if (QFileInfo(m_singleFileName).exists() && !(m_force || m_append)) {
Chris@604 173 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 174 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 175 return "";
Chris@498 176 }
Chris@498 177 return m_singleFileName;
Chris@498 178 }
Chris@498 179
Chris@498 180 if (m_stdout) return "";
Chris@498 181
Christophe@615 182 QUrl url(trackId, QUrl::StrictMode);
Chris@498 183 QString scheme = url.scheme().toLower();
Chris@498 184 bool local = (scheme == "" || scheme == "file" || scheme.length() == 1);
Chris@498 185
Chris@498 186 QString dirname, basename;
Chris@498 187 QString infilename = url.toLocalFile();
Chris@519 188 if (infilename == "") {
Chris@519 189 infilename = url.path();
Chris@519 190 }
Christophe@615 191 basename = QFileInfo(infilename).completeBaseName();
Chris@519 192 if (scheme.length() == 1) {
Chris@519 193 infilename = scheme + ":" + infilename; // DOS drive!
Chris@519 194 }
Chris@507 195
Chris@625 196 // cerr << "trackId = " << trackId.toStdString() << ", url = " << url.toString().toStdString() << ", infilename = "
Chris@625 197 // << infilename.toStdString() << ", basename = " << basename.toStdString() << ", m_baseDir = " << m_baseDir.toStdString() << endl;
Chris@498 198
Chris@498 199 if (m_baseDir != "") dirname = QFileInfo(m_baseDir).absoluteFilePath();
Chris@498 200 else if (local) dirname = QFileInfo(infilename).absolutePath();
Chris@498 201 else dirname = QDir::currentPath();
Chris@498 202
Chris@625 203 // cerr << "dirname = " << dirname.toStdString() << endl;
Chris@604 204
Chris@498 205 QString filename;
Chris@498 206
Chris@498 207 if (m_manyFiles && transformId != "") {
Chris@514 208 filename = QString("%1_%2.%3").arg(basename).arg(transformId).arg(m_extension);
Chris@498 209 } else {
Chris@498 210 filename = QString("%1.%2").arg(basename).arg(m_extension);
Chris@498 211 }
Chris@498 212
Chris@514 213 filename.replace(':', '_'); // ':' not permitted in Windows
Chris@514 214
Chris@498 215 filename = QDir(dirname).filePath(filename);
Chris@498 216
Chris@498 217 if (QFileInfo(filename).exists() && !(m_force || m_append)) {
Chris@604 218 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 219 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 220 return "";
Chris@498 221 }
Chris@498 222
Chris@498 223 return filename;
Chris@498 224 }
Chris@498 225
Chris@625 226 void
Chris@625 227 FileFeatureWriter::testOutputFile(QString trackId,
Chris@625 228 TransformId transformId)
Chris@625 229 {
Chris@626 230 // Obviously, if we're writing to stdout we can't test for an
Chris@626 231 // openable output file. But when writing a single file we don't
Chris@626 232 // want to either, because this test would fail on the second and
Chris@626 233 // subsequent input files (because the file would already exist).
Chris@626 234 // getOutputFile does the right thing in this case, so we just
Chris@626 235 // leave it to it
Chris@626 236 if (m_stdout || m_singleFileName != "") return;
Chris@626 237
Chris@625 238 QString filename = getOutputFilename(trackId, transformId);
Chris@625 239 if (filename == "") {
Chris@625 240 throw FailedToOpenOutputStream(trackId, transformId);
Chris@625 241 }
Chris@625 242 }
Chris@498 243
Chris@625 244 QFile *
Chris@625 245 FileFeatureWriter::getOutputFile(QString trackId,
Chris@625 246 TransformId transformId)
Chris@498 247 {
Chris@498 248 pair<QString, TransformId> key;
Chris@498 249
Chris@498 250 if (m_singleFileName != "") {
Chris@498 251 key = pair<QString, TransformId>("", "");
Chris@498 252 } else if (m_manyFiles) {
Chris@498 253 key = pair<QString, TransformId>(trackId, transformId);
Chris@498 254 } else {
Chris@498 255 key = pair<QString, TransformId>(trackId, "");
Chris@498 256 }
Chris@498 257
Chris@498 258 if (m_files.find(key) == m_files.end()) {
Chris@498 259
Chris@498 260 QString filename = getOutputFilename(trackId, transformId);
Chris@498 261
Chris@625 262 if (filename == "") { // stdout or failure
Chris@498 263 return 0;
Chris@498 264 }
Chris@498 265
Chris@498 266 cerr << "FileFeatureWriter: NOTE: Using output filename \""
Chris@498 267 << filename.toStdString() << "\"" << endl;
Chris@498 268
Chris@591 269 if (m_append) {
Chris@591 270 cerr << "FileFeatureWriter: NOTE: Calling reviewFileForAppending" << endl;
Chris@591 271 reviewFileForAppending(filename);
Chris@591 272 }
Chris@591 273
Chris@498 274 QFile *file = new QFile(filename);
Chris@498 275 QIODevice::OpenMode mode = (QIODevice::WriteOnly);
Chris@498 276 if (m_append) mode |= QIODevice::Append;
Chris@498 277
Chris@498 278 if (!file->open(mode)) {
Chris@498 279 cerr << "FileFeatureWriter: ERROR: Failed to open output file \"" << filename.toStdString()
Chris@498 280 << "\" for writing" << endl;
Chris@498 281 delete file;
Chris@498 282 m_files[key] = 0;
Chris@498 283 throw FailedToOpenFile(filename);
Chris@498 284 }
Chris@626 285
Chris@498 286 m_files[key] = file;
Chris@498 287 }
Chris@498 288
Chris@498 289 return m_files[key];
Chris@498 290 }
Chris@498 291
Chris@498 292
Chris@498 293 QTextStream *FileFeatureWriter::getOutputStream(QString trackId,
Chris@498 294 TransformId transformId)
Chris@498 295 {
Chris@498 296 QFile *file = getOutputFile(trackId, transformId);
Chris@498 297 if (!file && !m_stdout) {
Chris@498 298 return 0;
Chris@498 299 }
Chris@626 300
Chris@498 301 if (m_streams.find(file) == m_streams.end()) {
Chris@498 302 if (m_stdout) {
Chris@498 303 m_streams[file] = new QTextStream(stdout);
Chris@498 304 } else {
Chris@498 305 m_streams[file] = new QTextStream(file);
Chris@498 306 }
Chris@498 307 }
Chris@498 308
Chris@512 309 QTextStream *stream = m_streams[file];
Chris@512 310
Chris@512 311 if (m_prevstream && stream != m_prevstream) {
Chris@512 312 m_prevstream->flush();
Chris@512 313 }
Chris@512 314 m_prevstream = stream;
Chris@512 315
Chris@512 316 return stream;
Chris@498 317 }
Chris@498 318
Chris@515 319
Chris@515 320 void
Chris@515 321 FileFeatureWriter::flush()
Chris@515 322 {
Chris@515 323 if (m_prevstream) {
Chris@515 324 m_prevstream->flush();
Chris@515 325 }
Chris@515 326 }
Chris@515 327
Chris@531 328
Chris@531 329 void
Chris@531 330 FileFeatureWriter::finish()
Chris@531 331 {
Chris@579 332 // cerr << "FileFeatureWriter::finish()" << endl;
Chris@531 333
Chris@531 334 if (m_singleFileName != "" || m_stdout) return;
Chris@531 335
Chris@531 336 while (!m_streams.empty()) {
Chris@531 337 m_streams.begin()->second->flush();
Chris@531 338 delete m_streams.begin()->second;
Chris@531 339 m_streams.erase(m_streams.begin());
Chris@531 340 }
Chris@531 341 while (!m_files.empty()) {
Chris@625 342 if (m_files.begin()->second) {
Chris@625 343 cerr << "FileFeatureWriter::finish: NOTE: Closing feature file \""
Chris@625 344 << m_files.begin()->second->fileName().toStdString() << "\"" << endl;
Chris@625 345 delete m_files.begin()->second;
Chris@625 346 }
Chris@531 347 m_files.erase(m_files.begin());
Chris@531 348 }
Chris@531 349 m_prevstream = 0;
Chris@531 350 }
Chris@531 351