annotate transform/FileFeatureWriter.cpp @ 1858:14747f24ad04

Use cancellable serialiser; add some more profiling points
author Chris Cannam
date Thu, 14 May 2020 16:36:48 +0100
parents 70e172e6cc59
children
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@1582 35 m_prevstream(nullptr),
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@1428 49 SVCERR << "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@999 78 p.description = "Base output directory path. (The default is the same directory as the input file.) The directory must exist already.";
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@999 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@999 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@999 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@1428 133 SVCERR << "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@1428 147 // SVCERR << "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@1428 156 SVCERR << "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@999 170 FileFeatureWriter::createOutputFilename(QString trackId,
Chris@999 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@1428 175 SVCERR << endl << "FileFeatureWriter: ERROR: Specified output file \"" << m_singleFileName << "\" exists and neither --" << getWriterTag() << "-force nor --" << getWriterTag() << "-append flag is specified -- not overwriting" << endl;
Chris@1428 176 SVCERR << "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@999 182 if (m_stdout) {
Chris@999 183 return "";
Chris@999 184 }
Chris@498 185
Christophe@615 186 QUrl url(trackId, QUrl::StrictMode);
Chris@498 187 QString scheme = url.scheme().toLower();
Chris@498 188 bool local = (scheme == "" || scheme == "file" || scheme.length() == 1);
Chris@498 189
Chris@498 190 QString dirname, basename;
Chris@498 191 QString infilename = url.toLocalFile();
Chris@519 192 if (infilename == "") {
Chris@519 193 infilename = url.path();
Chris@519 194 }
Christophe@615 195 basename = QFileInfo(infilename).completeBaseName();
Chris@519 196 if (scheme.length() == 1) {
Chris@519 197 infilename = scheme + ":" + infilename; // DOS drive!
Chris@519 198 }
Chris@507 199
Chris@686 200 // cerr << "trackId = " << trackId << ", url = " << url.toString() << ", infilename = "
Chris@686 201 // << infilename << ", basename = " << basename << ", m_baseDir = " << m_baseDir << endl;
Chris@498 202
Chris@498 203 if (m_baseDir != "") dirname = QFileInfo(m_baseDir).absoluteFilePath();
Chris@498 204 else if (local) dirname = QFileInfo(infilename).absolutePath();
Chris@498 205 else dirname = QDir::currentPath();
Chris@498 206
Chris@686 207 // cerr << "dirname = " << dirname << endl;
Chris@604 208
Chris@498 209 QString filename;
Chris@498 210
Chris@498 211 if (m_manyFiles && transformId != "") {
Chris@514 212 filename = QString("%1_%2.%3").arg(basename).arg(transformId).arg(m_extension);
Chris@498 213 } else {
Chris@498 214 filename = QString("%1.%2").arg(basename).arg(m_extension);
Chris@498 215 }
Chris@498 216
Chris@514 217 filename.replace(':', '_'); // ':' not permitted in Windows
Chris@514 218
Chris@498 219 filename = QDir(dirname).filePath(filename);
Chris@498 220
Chris@498 221 if (QFileInfo(filename).exists() && !(m_force || m_append)) {
Chris@1428 222 SVCERR << 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@1428 223 SVCERR << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
Chris@498 224 return "";
Chris@498 225 }
Chris@498 226
Chris@498 227 return filename;
Chris@498 228 }
Chris@498 229
Chris@625 230 void
Chris@625 231 FileFeatureWriter::testOutputFile(QString trackId,
Chris@625 232 TransformId transformId)
Chris@625 233 {
Chris@626 234 // Obviously, if we're writing to stdout we can't test for an
Chris@626 235 // openable output file. But when writing a single file we don't
Chris@626 236 // want to either, because this test would fail on the second and
Chris@626 237 // subsequent input files (because the file would already exist).
Chris@626 238 // getOutputFile does the right thing in this case, so we just
Chris@626 239 // leave it to it
Chris@626 240 if (m_stdout || m_singleFileName != "") return;
Chris@626 241
Chris@999 242 QString filename = createOutputFilename(trackId, transformId);
Chris@625 243 if (filename == "") {
Chris@625 244 throw FailedToOpenOutputStream(trackId, transformId);
Chris@625 245 }
Chris@625 246 }
Chris@498 247
Chris@999 248 FileFeatureWriter::TrackTransformPair
Chris@999 249 FileFeatureWriter::getFilenameKey(QString trackId,
Chris@999 250 TransformId transformId)
Chris@999 251 {
Chris@999 252 TrackTransformPair key;
Chris@999 253
Chris@999 254 if (m_singleFileName != "") {
Chris@999 255 key = TrackTransformPair("", "");
Chris@999 256 } else if (m_manyFiles) {
Chris@999 257 key = TrackTransformPair(trackId, transformId);
Chris@999 258 } else {
Chris@999 259 key = TrackTransformPair(trackId, "");
Chris@999 260 }
Chris@999 261
Chris@999 262 return key;
Chris@999 263 }
Chris@999 264
Chris@999 265 QString
Chris@999 266 FileFeatureWriter::getOutputFilename(QString trackId,
Chris@999 267 TransformId transformId)
Chris@999 268 {
Chris@999 269 TrackTransformPair key = getFilenameKey(trackId, transformId);
Chris@999 270 if (m_filenames.find(key) == m_filenames.end()) {
Chris@999 271 m_filenames[key] = createOutputFilename(trackId, transformId);
Chris@999 272 }
Chris@999 273 return m_filenames[key];
Chris@999 274 }
Chris@999 275
Chris@625 276 QFile *
Chris@625 277 FileFeatureWriter::getOutputFile(QString trackId,
Chris@625 278 TransformId transformId)
Chris@498 279 {
Chris@999 280 TrackTransformPair key = getFilenameKey(trackId, transformId);
Chris@498 281
Chris@498 282 if (m_files.find(key) == m_files.end()) {
Chris@498 283
Chris@999 284 QString filename = createOutputFilename(trackId, transformId);
Chris@498 285
Chris@625 286 if (filename == "") { // stdout or failure
Chris@1582 287 return nullptr;
Chris@498 288 }
Chris@498 289
Chris@690 290 SVDEBUG << "FileFeatureWriter: NOTE: Using output filename \""
Chris@686 291 << filename << "\"" << endl;
Chris@498 292
Chris@591 293 if (m_append) {
Chris@690 294 SVDEBUG << "FileFeatureWriter: NOTE: Calling reviewFileForAppending" << endl;
Chris@591 295 reviewFileForAppending(filename);
Chris@591 296 }
Chris@591 297
Chris@498 298 QFile *file = new QFile(filename);
Chris@498 299 QIODevice::OpenMode mode = (QIODevice::WriteOnly);
Chris@498 300 if (m_append) mode |= QIODevice::Append;
Chris@498 301
Chris@498 302 if (!file->open(mode)) {
Chris@1428 303 SVCERR << "FileFeatureWriter: ERROR: Failed to open output file \"" << filename
Chris@498 304 << "\" for writing" << endl;
Chris@498 305 delete file;
Chris@1582 306 m_files[key] = nullptr;
Chris@498 307 throw FailedToOpenFile(filename);
Chris@498 308 }
Chris@626 309
Chris@498 310 m_files[key] = file;
Chris@498 311 }
Chris@498 312
Chris@498 313 return m_files[key];
Chris@498 314 }
Chris@498 315
Chris@498 316
Chris@498 317 QTextStream *FileFeatureWriter::getOutputStream(QString trackId,
Chris@1035 318 TransformId transformId,
Chris@1035 319 QTextCodec *codec)
Chris@498 320 {
Chris@498 321 QFile *file = getOutputFile(trackId, transformId);
Chris@498 322 if (!file && !m_stdout) {
Chris@1582 323 return nullptr;
Chris@498 324 }
Chris@626 325
Chris@498 326 if (m_streams.find(file) == m_streams.end()) {
Chris@498 327 if (m_stdout) {
Chris@498 328 m_streams[file] = new QTextStream(stdout);
Chris@498 329 } else {
Chris@498 330 m_streams[file] = new QTextStream(file);
Chris@498 331 }
Chris@1035 332 m_streams[file]->setCodec(codec);
Chris@498 333 }
Chris@498 334
Chris@512 335 QTextStream *stream = m_streams[file];
Chris@512 336
Chris@512 337 if (m_prevstream && stream != m_prevstream) {
Chris@512 338 m_prevstream->flush();
Chris@512 339 }
Chris@512 340 m_prevstream = stream;
Chris@512 341
Chris@512 342 return stream;
Chris@498 343 }
Chris@498 344
Chris@515 345
Chris@515 346 void
Chris@515 347 FileFeatureWriter::flush()
Chris@515 348 {
Chris@515 349 if (m_prevstream) {
Chris@515 350 m_prevstream->flush();
Chris@515 351 }
Chris@515 352 }
Chris@515 353
Chris@531 354
Chris@531 355 void
Chris@531 356 FileFeatureWriter::finish()
Chris@531 357 {
Chris@690 358 // SVDEBUG << "FileFeatureWriter::finish()" << endl;
Chris@531 359
Chris@531 360 if (m_singleFileName != "" || m_stdout) return;
Chris@531 361
Chris@531 362 while (!m_streams.empty()) {
Chris@531 363 m_streams.begin()->second->flush();
Chris@531 364 delete m_streams.begin()->second;
Chris@531 365 m_streams.erase(m_streams.begin());
Chris@531 366 }
Chris@531 367 while (!m_files.empty()) {
Chris@625 368 if (m_files.begin()->second) {
Chris@690 369 SVDEBUG << "FileFeatureWriter::finish: NOTE: Closing feature file \""
Chris@686 370 << m_files.begin()->second->fileName() << "\"" << endl;
Chris@625 371 delete m_files.begin()->second;
Chris@625 372 }
Chris@531 373 m_files.erase(m_files.begin());
Chris@531 374 }
Chris@1582 375 m_prevstream = nullptr;
Chris@531 376 }
Chris@531 377