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