svcore  1.9
FileFeatureWriter.cpp
Go to the documentation of this file.
00001 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
00002 
00003 /*
00004     Sonic Visualiser
00005     An audio file viewer and annotation editor.
00006 
00007     Sonic Annotator
00008     A utility for batch feature extraction from audio files.
00009 
00010     Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
00011     Copyright 2007-2008 QMUL.
00012 
00013     This program is free software; you can redistribute it and/or
00014     modify it under the terms of the GNU General Public License as
00015     published by the Free Software Foundation; either version 2 of the
00016     License, or (at your option) any later version.  See the file
00017     COPYING included with this distribution for more information.
00018 */
00019 
00020 #include "FileFeatureWriter.h"
00021 
00022 #include "base/Exceptions.h"
00023 
00024 #include <QTextStream>
00025 #include <QFile>
00026 #include <QFileInfo>
00027 #include <QUrl>
00028 #include <QDir>
00029 
00030 using namespace std;
00031 using namespace Vamp;
00032 
00033 FileFeatureWriter::FileFeatureWriter(int support,
00034                                      QString extension) :
00035     m_prevstream(0),
00036     m_support(support),
00037     m_extension(extension),
00038     m_manyFiles(false),
00039     m_stdout(false),
00040     m_append(false),
00041     m_force(false)
00042 {
00043     if (!(m_support & SupportOneFilePerTrack)) {
00044         if (m_support & SupportOneFilePerTrackTransform) {
00045             m_manyFiles = true;
00046         } else if (m_support & SupportOneFileTotal) {
00047             m_singleFileName = QString("output.%1").arg(m_extension);
00048         } else {
00049             SVDEBUG << "FileFeatureWriter::FileFeatureWriter: ERROR: Invalid support specification " << support << endl;
00050         }
00051     }
00052 }
00053 
00054 FileFeatureWriter::~FileFeatureWriter()
00055 {
00056     while (!m_streams.empty()) {
00057         m_streams.begin()->second->flush();
00058         delete m_streams.begin()->second;
00059         m_streams.erase(m_streams.begin());
00060     }
00061     while (!m_files.empty()) {
00062         if (m_files.begin()->second) {
00063             SVDEBUG << "FileFeatureWriter::~FileFeatureWriter: NOTE: Closing feature file \""
00064                  << m_files.begin()->second->fileName() << "\"" << endl;
00065             delete m_files.begin()->second;
00066         }
00067         m_files.erase(m_files.begin());
00068     }
00069 }
00070 
00071 FileFeatureWriter::ParameterList
00072 FileFeatureWriter::getSupportedParameters() const
00073 {
00074     ParameterList pl;
00075     Parameter p;
00076 
00077     p.name = "basedir";
00078     p.description = "Base output directory path.  (The default is the same directory as the input file.)";
00079     p.hasArg = true;
00080     pl.push_back(p);
00081 
00082     if (m_support & SupportOneFilePerTrackTransform &&
00083         m_support & SupportOneFilePerTrack) {
00084         p.name = "many-files";
00085         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.)";
00086         p.hasArg = false;
00087         pl.push_back(p);
00088     }
00089 
00090     if (m_support & SupportOneFileTotal) {
00091         if (m_support & ~SupportOneFileTotal) { // not only option
00092             p.name = "one-file";
00093             if (m_support & SupportOneFilePerTrack) {
00094                 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.)";
00095             } else {
00096                 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.)";
00097             }                
00098             p.hasArg = true;
00099             pl.push_back(p);
00100         }
00101         p.name = "stdout";
00102         p.description = "Write all transform results directly to standard output.";
00103         p.hasArg = false;
00104         pl.push_back(p);
00105     }
00106 
00107     p.name = "force";
00108     p.description = "If an output file already exists, overwrite it.";
00109     p.hasArg = false;
00110     pl.push_back(p);
00111 
00112     p.name = "append";
00113     p.description = "If an output file already exists, append data to it.";
00114     p.hasArg = false;
00115     pl.push_back(p);
00116 
00117     return pl;
00118 }
00119 
00120 void
00121 FileFeatureWriter::setParameters(map<string, string> &params)
00122 {
00123     for (map<string, string>::iterator i = params.begin();
00124          i != params.end(); ++i) {
00125         if (i->first == "basedir") {
00126             m_baseDir = i->second.c_str();
00127         } else if (i->first == "many-files") {
00128             if (m_support & SupportOneFilePerTrackTransform &&
00129                 m_support & SupportOneFilePerTrack) {
00130                 if (m_singleFileName != "") {
00131                     SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both one-file and many-files parameters provided, ignoring many-files" << endl;
00132                 } else {
00133                     m_manyFiles = true;
00134                 }
00135             }
00136         } else if (i->first == "one-file") {
00137             if (m_support & SupportOneFileTotal) {
00138                 if (m_support & ~SupportOneFileTotal) { // not only option
00139                     // No, we cannot do this test because m_manyFiles
00140                     // may be on by default (for any FileFeatureWriter
00141                     // that supports OneFilePerTrackTransform but not
00142                     // OneFilePerTrack), so we need to be able to
00143                     // override it
00144 //                    if (m_manyFiles) {
00145 //                        SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both many-files and one-file parameters provided, ignoring one-file" << endl;
00146 //                    } else {
00147                         m_singleFileName = i->second.c_str();
00148 //                    }
00149                 }
00150             }
00151         } else if (i->first == "stdout") {
00152             if (m_support & SupportOneFileTotal) {
00153                 if (m_singleFileName != "") {
00154                     SVDEBUG << "FileFeatureWriter::setParameters: WARNING: Both stdout and one-file provided, ignoring stdout" << endl;
00155                 } else {
00156                     m_stdout = true;
00157                 }
00158             }
00159         } else if (i->first == "append") {
00160             m_append = true;
00161         } else if (i->first == "force") {
00162             m_force = true;
00163         }
00164     }
00165 }
00166 
00167 QString
00168 FileFeatureWriter::getOutputFilename(QString trackId,
00169                                      TransformId transformId)
00170 {
00171     if (m_singleFileName != "") {
00172         if (QFileInfo(m_singleFileName).exists() && !(m_force || m_append)) {
00173             cerr << endl << "FileFeatureWriter: ERROR: Specified output file \"" << m_singleFileName << "\" exists and neither --" << getWriterTag() << "-force nor --" << getWriterTag() << "-append flag is specified -- not overwriting" << endl;
00174             SVDEBUG << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
00175             return "";
00176         }
00177         return m_singleFileName;
00178     }
00179 
00180     if (m_stdout) return "";
00181     
00182     QUrl url(trackId, QUrl::StrictMode);
00183     QString scheme = url.scheme().toLower();
00184     bool local = (scheme == "" || scheme == "file" || scheme.length() == 1);
00185 
00186     QString dirname, basename;
00187     QString infilename = url.toLocalFile();
00188     if (infilename == "") {
00189         infilename = url.path();
00190     }
00191     basename = QFileInfo(infilename).completeBaseName();
00192     if (scheme.length() == 1) {
00193         infilename = scheme + ":" + infilename; // DOS drive!
00194     }
00195 
00196 //    cerr << "trackId = " << trackId << ", url = " << url.toString() << ", infilename = "
00197 //         << infilename << ", basename = " << basename << ", m_baseDir = " << m_baseDir << endl;
00198 
00199     if (m_baseDir != "") dirname = QFileInfo(m_baseDir).absoluteFilePath();
00200     else if (local) dirname = QFileInfo(infilename).absolutePath();
00201     else dirname = QDir::currentPath();
00202 
00203 //    cerr << "dirname = " << dirname << endl;
00204 
00205     QString filename;
00206 
00207     if (m_manyFiles && transformId != "") {
00208         filename = QString("%1_%2.%3").arg(basename).arg(transformId).arg(m_extension);
00209     } else {
00210         filename = QString("%1.%2").arg(basename).arg(m_extension);
00211     }
00212 
00213     filename.replace(':', '_'); // ':' not permitted in Windows
00214 
00215     filename = QDir(dirname).filePath(filename);
00216 
00217     if (QFileInfo(filename).exists() && !(m_force || m_append)) {
00218         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;
00219         SVDEBUG << "NOTE: To find out how to fix this problem, read the help for the --" << getWriterTag() << "-force" << endl << "and --" << getWriterTag() << "-append options" << endl;
00220         return "";
00221     }
00222     
00223     return filename;
00224 }
00225 
00226 void
00227 FileFeatureWriter::testOutputFile(QString trackId,
00228                                   TransformId transformId)
00229 {
00230     // Obviously, if we're writing to stdout we can't test for an
00231     // openable output file. But when writing a single file we don't
00232     // want to either, because this test would fail on the second and
00233     // subsequent input files (because the file would already exist).
00234     // getOutputFile does the right thing in this case, so we just
00235     // leave it to it
00236     if (m_stdout || m_singleFileName != "") return;
00237 
00238     QString filename = getOutputFilename(trackId, transformId);
00239     if (filename == "") {
00240         throw FailedToOpenOutputStream(trackId, transformId);
00241     }
00242 }
00243 
00244 QFile *
00245 FileFeatureWriter::getOutputFile(QString trackId,
00246                                  TransformId transformId)
00247 {
00248     pair<QString, TransformId> key;
00249 
00250     if (m_singleFileName != "") {
00251         key = pair<QString, TransformId>("", "");
00252     } else if (m_manyFiles) {
00253         key = pair<QString, TransformId>(trackId, transformId);
00254     } else {
00255         key = pair<QString, TransformId>(trackId, "");
00256     }
00257 
00258     if (m_files.find(key) == m_files.end()) {
00259 
00260         QString filename = getOutputFilename(trackId, transformId);
00261 
00262         if (filename == "") { // stdout or failure
00263             return 0;
00264         }
00265 
00266         SVDEBUG << "FileFeatureWriter: NOTE: Using output filename \""
00267              << filename << "\"" << endl;
00268 
00269         if (m_append) {
00270             SVDEBUG << "FileFeatureWriter: NOTE: Calling reviewFileForAppending" << endl;
00271             reviewFileForAppending(filename);
00272         }
00273         
00274         QFile *file = new QFile(filename);
00275         QIODevice::OpenMode mode = (QIODevice::WriteOnly);
00276         if (m_append) mode |= QIODevice::Append;
00277                        
00278         if (!file->open(mode)) {
00279             cerr << "FileFeatureWriter: ERROR: Failed to open output file \"" << filename
00280                  << "\" for writing" << endl;
00281             delete file;
00282             m_files[key] = 0;
00283             throw FailedToOpenFile(filename);
00284         }
00285         
00286         m_files[key] = file;
00287     }
00288 
00289     return m_files[key];
00290 }
00291 
00292 
00293 QTextStream *FileFeatureWriter::getOutputStream(QString trackId,
00294                                                TransformId transformId)
00295 {
00296     QFile *file = getOutputFile(trackId, transformId);
00297     if (!file && !m_stdout) {
00298         return 0;
00299     }
00300     
00301     if (m_streams.find(file) == m_streams.end()) {
00302         if (m_stdout) {
00303             m_streams[file] = new QTextStream(stdout);
00304         } else {
00305             m_streams[file] = new QTextStream(file);
00306         }
00307     }
00308 
00309     QTextStream *stream = m_streams[file];
00310 
00311     if (m_prevstream && stream != m_prevstream) {
00312         m_prevstream->flush();
00313     }
00314     m_prevstream = stream;
00315 
00316     return stream;
00317 }
00318             
00319 
00320 void
00321 FileFeatureWriter::flush()
00322 {
00323     if (m_prevstream) {
00324         m_prevstream->flush();
00325     }
00326 }
00327 
00328 
00329 void
00330 FileFeatureWriter::finish()
00331 {
00332 //    SVDEBUG << "FileFeatureWriter::finish()" << endl;
00333 
00334     if (m_singleFileName != "" || m_stdout) return;
00335 
00336     while (!m_streams.empty()) {
00337         m_streams.begin()->second->flush();
00338         delete m_streams.begin()->second;
00339         m_streams.erase(m_streams.begin());
00340     }
00341     while (!m_files.empty()) {
00342         if (m_files.begin()->second) {
00343             SVDEBUG << "FileFeatureWriter::finish: NOTE: Closing feature file \""
00344                  << m_files.begin()->second->fileName() << "\"" << endl;
00345             delete m_files.begin()->second;
00346         }
00347         m_files.erase(m_files.begin());
00348     }
00349     m_prevstream = 0;
00350 }
00351