annotate transform/FileFeatureWriter.cpp @ 997:2104ea2204d2

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