| 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@604 | 62         cerr << "FileFeatureWriter::~FileFeatureWriter: NOTE: Closing feature file \"" | 
| Chris@604 | 63              << m_files.begin()->second->fileName().toStdString() << "\"" << endl; | 
| Chris@498 | 64         delete m_files.begin()->second; | 
| Chris@498 | 65         m_files.erase(m_files.begin()); | 
| Chris@498 | 66     } | 
| Chris@498 | 67 } | 
| Chris@498 | 68 | 
| Chris@498 | 69 FileFeatureWriter::ParameterList | 
| Chris@498 | 70 FileFeatureWriter::getSupportedParameters() const | 
| Chris@498 | 71 { | 
| Chris@498 | 72     ParameterList pl; | 
| Chris@498 | 73     Parameter p; | 
| Chris@498 | 74 | 
| Chris@498 | 75     p.name = "basedir"; | 
| Chris@498 | 76     p.description = "Base output directory path.  (The default is the same directory as the input file.)"; | 
| Chris@498 | 77     p.hasArg = true; | 
| Chris@498 | 78     pl.push_back(p); | 
| Chris@498 | 79 | 
| Chris@498 | 80     if (m_support & SupportOneFilePerTrackTransform && | 
| Chris@498 | 81         m_support & SupportOneFilePerTrack) { | 
| Chris@498 | 82         p.name = "many-files"; | 
| Chris@498 | 83         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 | 84         p.hasArg = false; | 
| Chris@498 | 85         pl.push_back(p); | 
| Chris@498 | 86     } | 
| Chris@498 | 87 | 
| Chris@498 | 88     if (m_support & SupportOneFileTotal) { | 
| Chris@498 | 89         if (m_support & ~SupportOneFileTotal) { // not only option | 
| Chris@498 | 90             p.name = "one-file"; | 
| Chris@498 | 91             p.description = "Write all transform results for all input files into the single named output file."; | 
| Chris@498 | 92             p.hasArg = true; | 
| Chris@498 | 93             pl.push_back(p); | 
| Chris@498 | 94         } | 
| Chris@498 | 95         p.name = "stdout"; | 
| Chris@498 | 96         p.description = "Write all transform results directly to standard output."; | 
| Chris@498 | 97         p.hasArg = false; | 
| Chris@498 | 98         pl.push_back(p); | 
| Chris@498 | 99     } | 
| Chris@498 | 100 | 
| Chris@498 | 101     p.name = "force"; | 
| Chris@498 | 102     p.description = "If an output file already exists, overwrite it."; | 
| Chris@498 | 103     p.hasArg = false; | 
| Chris@498 | 104     pl.push_back(p); | 
| Chris@498 | 105 | 
| Chris@498 | 106     p.name = "append"; | 
| Chris@498 | 107     p.description = "If an output file already exists, append data to it."; | 
| Chris@498 | 108     p.hasArg = false; | 
| Chris@498 | 109     pl.push_back(p); | 
| Chris@498 | 110 | 
| Chris@498 | 111     return pl; | 
| Chris@498 | 112 } | 
| Chris@498 | 113 | 
| Chris@498 | 114 void | 
| Chris@498 | 115 FileFeatureWriter::setParameters(map<string, string> ¶ms) | 
| Chris@498 | 116 { | 
| Chris@498 | 117     for (map<string, string>::iterator i = params.begin(); | 
| Chris@498 | 118          i != params.end(); ++i) { | 
| Chris@498 | 119         if (i->first == "basedir") { | 
| Chris@498 | 120             m_baseDir = i->second.c_str(); | 
| Chris@498 | 121         } else if (i->first == "many-files") { | 
| Chris@498 | 122             if (m_support & SupportOneFilePerTrackTransform && | 
| Chris@498 | 123                 m_support & SupportOneFilePerTrack) { | 
| Chris@498 | 124                 if (m_singleFileName != "") { | 
| Chris@498 | 125                     cerr << "FileFeatureWriter::setParameters: WARNING: Both one-file and many-files parameters provided, ignoring many-files" << endl; | 
| Chris@498 | 126                 } else { | 
| Chris@498 | 127                     m_manyFiles = true; | 
| Chris@498 | 128                 } | 
| Chris@498 | 129             } | 
| Chris@498 | 130         } else if (i->first == "one-file") { | 
| Chris@498 | 131             if (m_support & SupportOneFileTotal) { | 
| Chris@498 | 132                 if (m_support & ~SupportOneFileTotal) { // not only option | 
| Chris@498 | 133                     if (m_manyFiles) { | 
| Chris@498 | 134                         cerr << "FileFeatureWriter::setParameters: WARNING: Both many-files and one-file parameters provided, ignoring one-file" << endl; | 
| Chris@498 | 135                     } else { | 
| Chris@498 | 136                         m_singleFileName = i->second.c_str(); | 
| Chris@498 | 137                     } | 
| Chris@498 | 138                 } | 
| Chris@498 | 139             } | 
| Chris@498 | 140         } else if (i->first == "stdout") { | 
| Chris@498 | 141             if (m_support & SupportOneFileTotal) { | 
| Chris@498 | 142                 if (m_singleFileName != "") { | 
| Chris@498 | 143                     cerr << "FileFeatureWriter::setParameters: WARNING: Both stdout and one-file provided, ignoring stdout" << endl; | 
| Chris@498 | 144                 } else { | 
| Chris@498 | 145                     m_stdout = true; | 
| Chris@498 | 146                 } | 
| Chris@498 | 147             } | 
| Chris@498 | 148         } else if (i->first == "append") { | 
| Chris@498 | 149             m_append = true; | 
| Chris@498 | 150         } else if (i->first == "force") { | 
| Chris@498 | 151             m_force = true; | 
| Chris@498 | 152         } | 
| Chris@498 | 153     } | 
| Chris@498 | 154 } | 
| Chris@498 | 155 | 
| Chris@498 | 156 QString FileFeatureWriter::getOutputFilename(QString trackId, | 
| Chris@498 | 157                                              TransformId transformId) | 
| Chris@498 | 158 { | 
| Chris@498 | 159     if (m_singleFileName != "") { | 
| Chris@498 | 160         if (QFileInfo(m_singleFileName).exists() && !(m_force || m_append)) { | 
| Chris@604 | 161             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 | 162             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 | 163             return ""; | 
| Chris@498 | 164         } | 
| Chris@498 | 165         return m_singleFileName; | 
| Chris@498 | 166     } | 
| Chris@498 | 167 | 
| Chris@498 | 168     if (m_stdout) return ""; | 
| Chris@498 | 169 | 
| Christophe@615 | 170     QUrl url(trackId, QUrl::StrictMode); | 
| Chris@498 | 171     QString scheme = url.scheme().toLower(); | 
| Chris@498 | 172     bool local = (scheme == "" || scheme == "file" || scheme.length() == 1); | 
| Chris@498 | 173 | 
| Chris@498 | 174     QString dirname, basename; | 
| Chris@498 | 175     QString infilename = url.toLocalFile(); | 
| Chris@519 | 176     if (infilename == "") { | 
| Chris@519 | 177         infilename = url.path(); | 
| Chris@519 | 178     } | 
| Christophe@615 | 179     basename = QFileInfo(infilename).completeBaseName(); | 
| Chris@519 | 180     if (scheme.length() == 1) { | 
| Chris@519 | 181         infilename = scheme + ":" + infilename; // DOS drive! | 
| Chris@519 | 182     } | 
| Chris@507 | 183 | 
| Chris@604 | 184     cerr << "trackId = " << trackId.toStdString() << ", url = " << url.toString().toStdString() << ", infilename = " | 
| Chris@604 | 185          << infilename.toStdString() << ", basename = " << basename.toStdString() << ", m_baseDir = " << m_baseDir.toStdString() << endl; | 
| Chris@498 | 186 | 
| Chris@498 | 187     if (m_baseDir != "") dirname = QFileInfo(m_baseDir).absoluteFilePath(); | 
| Chris@498 | 188     else if (local) dirname = QFileInfo(infilename).absolutePath(); | 
| Chris@498 | 189     else dirname = QDir::currentPath(); | 
| Chris@498 | 190 | 
| Chris@604 | 191     cerr << "dirname = " << dirname.toStdString() << endl; | 
| Chris@604 | 192 | 
| Chris@498 | 193     QString filename; | 
| Chris@498 | 194 | 
| Chris@498 | 195     if (m_manyFiles && transformId != "") { | 
| Chris@514 | 196         filename = QString("%1_%2.%3").arg(basename).arg(transformId).arg(m_extension); | 
| Chris@498 | 197     } else { | 
| Chris@498 | 198         filename = QString("%1.%2").arg(basename).arg(m_extension); | 
| Chris@498 | 199     } | 
| Chris@498 | 200 | 
| Chris@514 | 201     filename.replace(':', '_'); // ':' not permitted in Windows | 
| Chris@514 | 202 | 
| Chris@498 | 203     filename = QDir(dirname).filePath(filename); | 
| Chris@498 | 204 | 
| Chris@498 | 205     if (QFileInfo(filename).exists() && !(m_force || m_append)) { | 
| Chris@604 | 206         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 | 207         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 | 208         return ""; | 
| Chris@498 | 209     } | 
| Chris@498 | 210 | 
| Chris@498 | 211     return filename; | 
| Chris@498 | 212 } | 
| Chris@498 | 213 | 
| Chris@498 | 214 | 
| Chris@498 | 215 QFile *FileFeatureWriter::getOutputFile(QString trackId, | 
| Chris@498 | 216                                         TransformId transformId) | 
| Chris@498 | 217 { | 
| Chris@498 | 218     pair<QString, TransformId> key; | 
| Chris@498 | 219 | 
| Chris@498 | 220     if (m_singleFileName != "") { | 
| Chris@498 | 221         key = pair<QString, TransformId>("", ""); | 
| Chris@498 | 222     } else if (m_manyFiles) { | 
| Chris@498 | 223         key = pair<QString, TransformId>(trackId, transformId); | 
| Chris@498 | 224     } else { | 
| Chris@498 | 225         key = pair<QString, TransformId>(trackId, ""); | 
| Chris@498 | 226     } | 
| Chris@498 | 227 | 
| Chris@498 | 228     if (m_files.find(key) == m_files.end()) { | 
| Chris@498 | 229 | 
| Chris@498 | 230         QString filename = getOutputFilename(trackId, transformId); | 
| Chris@498 | 231 | 
| Chris@498 | 232         if (filename == "") { // stdout | 
| Chris@498 | 233             return 0; | 
| Chris@498 | 234         } | 
| Chris@498 | 235 | 
| Chris@498 | 236         cerr << "FileFeatureWriter: NOTE: Using output filename \"" | 
| Chris@498 | 237              << filename.toStdString() << "\"" << endl; | 
| Chris@498 | 238 | 
| Chris@591 | 239         if (m_append) { | 
| Chris@591 | 240             cerr << "FileFeatureWriter: NOTE: Calling reviewFileForAppending" << endl; | 
| Chris@591 | 241             reviewFileForAppending(filename); | 
| Chris@591 | 242         } | 
| Chris@591 | 243 | 
| Chris@498 | 244         QFile *file = new QFile(filename); | 
| Chris@498 | 245         QIODevice::OpenMode mode = (QIODevice::WriteOnly); | 
| Chris@498 | 246         if (m_append) mode |= QIODevice::Append; | 
| Chris@498 | 247 | 
| Chris@498 | 248         if (!file->open(mode)) { | 
| Chris@498 | 249             cerr << "FileFeatureWriter: ERROR: Failed to open output file \"" << filename.toStdString() | 
| Chris@498 | 250                  << "\" for writing" << endl; | 
| Chris@498 | 251             delete file; | 
| Chris@498 | 252             m_files[key] = 0; | 
| Chris@498 | 253             throw FailedToOpenFile(filename); | 
| Chris@498 | 254         } | 
| Chris@498 | 255 | 
| Chris@498 | 256         m_files[key] = file; | 
| Chris@498 | 257     } | 
| Chris@498 | 258 | 
| Chris@498 | 259     return m_files[key]; | 
| Chris@498 | 260 } | 
| Chris@498 | 261 | 
| Chris@498 | 262 | 
| Chris@498 | 263 QTextStream *FileFeatureWriter::getOutputStream(QString trackId, | 
| Chris@498 | 264                                                TransformId transformId) | 
| Chris@498 | 265 { | 
| Chris@498 | 266     QFile *file = getOutputFile(trackId, transformId); | 
| Chris@498 | 267     if (!file && !m_stdout) { | 
| Chris@498 | 268         return 0; | 
| Chris@498 | 269     } | 
| Chris@498 | 270 | 
| Chris@498 | 271     if (m_streams.find(file) == m_streams.end()) { | 
| Chris@498 | 272         if (m_stdout) { | 
| Chris@498 | 273             m_streams[file] = new QTextStream(stdout); | 
| Chris@498 | 274         } else { | 
| Chris@498 | 275             m_streams[file] = new QTextStream(file); | 
| Chris@498 | 276         } | 
| Chris@498 | 277     } | 
| Chris@498 | 278 | 
| Chris@512 | 279     QTextStream *stream = m_streams[file]; | 
| Chris@512 | 280 | 
| Chris@512 | 281     if (m_prevstream && stream != m_prevstream) { | 
| Chris@512 | 282         m_prevstream->flush(); | 
| Chris@512 | 283     } | 
| Chris@512 | 284     m_prevstream = stream; | 
| Chris@512 | 285 | 
| Chris@512 | 286     return stream; | 
| Chris@498 | 287 } | 
| Chris@498 | 288 | 
| Chris@515 | 289 | 
| Chris@515 | 290 void | 
| Chris@515 | 291 FileFeatureWriter::flush() | 
| Chris@515 | 292 { | 
| Chris@515 | 293     if (m_prevstream) { | 
| Chris@515 | 294         m_prevstream->flush(); | 
| Chris@515 | 295     } | 
| Chris@515 | 296 } | 
| Chris@515 | 297 | 
| Chris@531 | 298 | 
| Chris@531 | 299 void | 
| Chris@531 | 300 FileFeatureWriter::finish() | 
| Chris@531 | 301 { | 
| Chris@579 | 302 //    cerr << "FileFeatureWriter::finish()" << endl; | 
| Chris@531 | 303 | 
| Chris@531 | 304     if (m_singleFileName != "" || m_stdout) return; | 
| Chris@531 | 305 | 
| Chris@531 | 306     while (!m_streams.empty()) { | 
| Chris@531 | 307         m_streams.begin()->second->flush(); | 
| Chris@531 | 308         delete m_streams.begin()->second; | 
| Chris@531 | 309         m_streams.erase(m_streams.begin()); | 
| Chris@531 | 310     } | 
| Chris@531 | 311     while (!m_files.empty()) { | 
| Chris@604 | 312         cerr << "FileFeatureWriter::finish: NOTE: Closing feature file \"" | 
| Chris@604 | 313              << m_files.begin()->second->fileName().toStdString() << "\"" << endl; | 
| Chris@531 | 314         delete m_files.begin()->second; | 
| Chris@531 | 315         m_files.erase(m_files.begin()); | 
| Chris@531 | 316     } | 
| Chris@531 | 317     m_prevstream = 0; | 
| Chris@531 | 318 } | 
| Chris@531 | 319 |