| 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> ¶ms) | 
| 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 |