annotate transform/CSVFeatureWriter.cpp @ 1035:d74ebd2d2c49

Require (and provide) text codec for output stream -- fixing #1153 (wrong codec used when writing RDF)
author Chris Cannam
date Mon, 02 Mar 2015 17:17:59 +0000
parents d954e03274e8
children b14064bd1f97
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 "CSVFeatureWriter.h"
Chris@498 21
Chris@498 22 #include <iostream>
Chris@498 23
Chris@498 24 #include <QRegExp>
Chris@498 25 #include <QTextStream>
Chris@1035 26 #include <QTextCodec>
Chris@498 27
Chris@498 28 using namespace std;
Chris@498 29 using namespace Vamp;
Chris@498 30
Chris@498 31 CSVFeatureWriter::CSVFeatureWriter() :
Chris@498 32 FileFeatureWriter(SupportOneFilePerTrackTransform |
Chris@997 33 SupportOneFileTotal |
Chris@997 34 SupportStdOut,
Chris@498 35 "csv"),
Chris@669 36 m_separator(","),
Chris@1000 37 m_sampleTiming(false),
Chris@1001 38 m_endTimes(false),
Chris@1002 39 m_forceEnd(false),
Chris@1002 40 m_omitFilename(false)
Chris@498 41 {
Chris@498 42 }
Chris@498 43
Chris@498 44 CSVFeatureWriter::~CSVFeatureWriter()
Chris@498 45 {
Chris@498 46 }
Chris@498 47
Chris@998 48 string
Chris@998 49 CSVFeatureWriter::getDescription() const
Chris@998 50 {
Chris@998 51 return "Write features in comma-separated (CSV) format. If transforms are being written to a single file or to stdout, the first column in the output will contain the input audio filename, or an empty string if the feature hails from the same audio file as its predecessor. If transforms are being written to multiple files, the audio filename column will be omitted. Subsequent columns will contain the feature timestamp, then any or all of duration, values, and label.";
Chris@998 52 }
Chris@998 53
Chris@498 54 CSVFeatureWriter::ParameterList
Chris@498 55 CSVFeatureWriter::getSupportedParameters() const
Chris@498 56 {
Chris@498 57 ParameterList pl = FileFeatureWriter::getSupportedParameters();
Chris@498 58 Parameter p;
Chris@498 59
Chris@498 60 p.name = "separator";
Chris@498 61 p.description = "Column separator for output. Default is \",\" (comma).";
Chris@498 62 p.hasArg = true;
Chris@498 63 pl.push_back(p);
Chris@669 64
Chris@1002 65 p.name = "omit-filename";
Chris@1002 66 p.description = "Omit the filename column. May result in confusion if sending more than one audio file's features to the same CSV output.";
Chris@1002 67 p.hasArg = false;
Chris@1002 68 pl.push_back(p);
Chris@1002 69
Chris@669 70 p.name = "sample-timing";
Chris@669 71 p.description = "Show timings as sample frame counts instead of in seconds.";
Chris@669 72 p.hasArg = false;
Chris@669 73 pl.push_back(p);
Chris@1000 74
Chris@1000 75 p.name = "end-times";
Chris@1000 76 p.description = "Show start and end time instead of start and duration, for features with duration.";
Chris@1000 77 p.hasArg = false;
Chris@1000 78 pl.push_back(p);
Chris@498 79
Chris@1001 80 p.name = "fill-ends";
Chris@1001 81 p.description = "Include durations (or end times) even for features without duration, by using the gap to the next feature instead.";
Chris@1001 82 p.hasArg = false;
Chris@1001 83 pl.push_back(p);
Chris@1001 84
Chris@498 85 return pl;
Chris@498 86 }
Chris@498 87
Chris@498 88 void
Chris@498 89 CSVFeatureWriter::setParameters(map<string, string> &params)
Chris@498 90 {
Chris@498 91 FileFeatureWriter::setParameters(params);
Chris@498 92
Chris@690 93 SVDEBUG << "CSVFeatureWriter::setParameters" << endl;
Chris@498 94 for (map<string, string>::iterator i = params.begin();
Chris@498 95 i != params.end(); ++i) {
Chris@498 96 cerr << i->first << " -> " << i->second << endl;
Chris@498 97 if (i->first == "separator") {
Chris@498 98 m_separator = i->second.c_str();
Chris@1002 99 cerr << "m_separator = " << m_separator << endl;
Chris@1002 100 if (m_separator == "\\t") {
Chris@1002 101 m_separator = QChar::Tabulation;
Chris@1002 102 }
Chris@669 103 } else if (i->first == "sample-timing") {
Chris@669 104 m_sampleTiming = true;
Chris@1000 105 } else if (i->first == "end-times") {
Chris@1000 106 m_endTimes = true;
Chris@1001 107 } else if (i->first == "fill-ends") {
Chris@1001 108 m_forceEnd = true;
Chris@1002 109 } else if (i->first == "omit-filename") {
Chris@1002 110 m_omitFilename = true;
Chris@498 111 }
Chris@498 112 }
Chris@498 113 }
Chris@498 114
Chris@498 115 void
Chris@498 116 CSVFeatureWriter::write(QString trackId,
Chris@498 117 const Transform &transform,
Chris@930 118 const Plugin::OutputDescriptor& ,
Chris@498 119 const Plugin::FeatureList& features,
Chris@498 120 std::string summaryType)
Chris@498 121 {
Chris@1001 122 TransformId transformId = transform.getIdentifier();
Chris@1001 123
Chris@498 124 // Select appropriate output file for our track/transform
Chris@498 125 // combination
Chris@498 126
Chris@1035 127 QTextStream *sptr = getOutputStream(trackId,
Chris@1035 128 transformId,
Chris@1035 129 QTextCodec::codecForName("UTF-8"));
Chris@604 130 if (!sptr) {
Chris@1001 131 throw FailedToOpenOutputStream(trackId, transformId);
Chris@604 132 }
Chris@498 133
Chris@498 134 QTextStream &stream = *sptr;
Chris@498 135
Chris@1001 136 int n = features.size();
Chris@498 137
Chris@1001 138 if (n == 0) return;
Chris@1001 139
Chris@1006 140 DataId tt(trackId, transform);
Chris@1001 141
Chris@1001 142 if (m_pending.find(tt) != m_pending.end()) {
Chris@1001 143 writeFeature(tt,
Chris@1001 144 stream,
Chris@1001 145 m_pending[tt],
Chris@1001 146 &features[0],
Chris@1001 147 m_pendingSummaryTypes[tt]);
Chris@1001 148 m_pending.erase(tt);
Chris@1001 149 m_pendingSummaryTypes.erase(tt);
Chris@1001 150 }
Chris@1001 151
Chris@1001 152 if (m_forceEnd) {
Chris@1001 153 // can't write final feature until we know its end time
Chris@1001 154 --n;
Chris@1001 155 m_pending[tt] = features[n];
Chris@1001 156 m_pendingSummaryTypes[tt] = summaryType;
Chris@1001 157 }
Chris@1001 158
Chris@1001 159 for (int i = 0; i < n; ++i) {
Chris@1001 160 writeFeature(tt,
Chris@1001 161 stream,
Chris@1001 162 features[i],
Chris@1001 163 m_forceEnd ? &features[i+1] : 0,
Chris@1001 164 summaryType);
Chris@1001 165 }
Chris@1001 166 }
Chris@1001 167
Chris@1001 168 void
Chris@1001 169 CSVFeatureWriter::finish()
Chris@1001 170 {
Chris@1001 171 for (PendingFeatures::const_iterator i = m_pending.begin();
Chris@1001 172 i != m_pending.end(); ++i) {
Chris@1006 173 DataId tt = i->first;
Chris@1001 174 Plugin::Feature f = i->second;
Chris@1035 175 QTextStream *sptr = getOutputStream(tt.first,
Chris@1035 176 tt.second.getIdentifier(),
Chris@1035 177 QTextCodec::codecForName("UTF-8"));
Chris@1001 178 if (!sptr) {
Chris@1006 179 throw FailedToOpenOutputStream(tt.first, tt.second.getIdentifier());
Chris@1001 180 }
Chris@1001 181 QTextStream &stream = *sptr;
Chris@1001 182 // final feature has its own time as end time (we can't
Chris@1001 183 // reliably determine the end of audio file, and because of
Chris@1001 184 // the nature of block processing, the feature could even
Chris@1001 185 // start beyond that anyway)
Chris@1001 186 writeFeature(tt, stream, f, &f, m_pendingSummaryTypes[tt]);
Chris@1001 187 }
Chris@1001 188
Chris@1001 189 m_pending.clear();
Chris@1001 190 }
Chris@1001 191
Chris@1001 192 void
Chris@1006 193 CSVFeatureWriter::writeFeature(DataId tt,
Chris@1001 194 QTextStream &stream,
Chris@1001 195 const Plugin::Feature &f,
Chris@1001 196 const Plugin::Feature *optionalNextFeature,
Chris@1001 197 std::string summaryType)
Chris@1001 198 {
Chris@1001 199 QString trackId = tt.first;
Chris@1006 200 Transform transform = tt.second;
Chris@1001 201
Chris@1002 202 if (!m_omitFilename) {
Chris@1002 203 if (m_stdout || m_singleFileName != "") {
Chris@1002 204 if (trackId != m_prevPrintedTrackId) {
Chris@1002 205 stream << "\"" << trackId << "\"" << m_separator;
Chris@1002 206 m_prevPrintedTrackId = trackId;
Chris@1002 207 } else {
Chris@1002 208 stream << m_separator;
Chris@1002 209 }
Chris@1001 210 }
Chris@1001 211 }
Chris@1001 212
Chris@1001 213 Vamp::RealTime duration;
Chris@1001 214 bool haveDuration = true;
Chris@1001 215
Chris@1001 216 if (f.hasDuration) {
Chris@1001 217 duration = f.duration;
Chris@1001 218 } else if (optionalNextFeature) {
Chris@1001 219 duration = optionalNextFeature->timestamp - f.timestamp;
Chris@1001 220 } else {
Chris@1001 221 haveDuration = false;
Chris@1001 222 }
Chris@1001 223
Chris@1001 224 if (m_sampleTiming) {
Chris@1001 225
Chris@1006 226 float rate = transform.getSampleRate();
Chris@1001 227
Chris@1001 228 stream << Vamp::RealTime::realTime2Frame(f.timestamp, rate);
Chris@1001 229
Chris@1001 230 if (haveDuration) {
Chris@1001 231 stream << m_separator;
Chris@1001 232 if (m_endTimes) {
Chris@1001 233 stream << Vamp::RealTime::realTime2Frame
Chris@1001 234 (f.timestamp + duration, rate);
Chris@514 235 } else {
Chris@1001 236 stream << Vamp::RealTime::realTime2Frame(duration, rate);
Chris@514 237 }
Chris@514 238 }
Chris@514 239
Chris@1001 240 } else {
Chris@498 241
Chris@1001 242 QString timestamp = f.timestamp.toString().c_str();
Chris@1001 243 timestamp.replace(QRegExp("^ +"), "");
Chris@1001 244 stream << timestamp;
Chris@669 245
Chris@1001 246 if (haveDuration) {
Chris@1001 247 if (m_endTimes) {
Chris@1001 248 QString endtime =
Chris@1001 249 (f.timestamp + duration).toString().c_str();
Chris@1001 250 endtime.replace(QRegExp("^ +"), "");
Chris@1001 251 stream << m_separator << endtime;
Chris@1001 252 } else {
Chris@1001 253 QString d = duration.toString().c_str();
Chris@1001 254 d.replace(QRegExp("^ +"), "");
Chris@1001 255 stream << m_separator << d;
Chris@669 256 }
Chris@1001 257 }
Chris@1001 258 }
Chris@669 259
Chris@1001 260 if (summaryType != "") {
Chris@1001 261 stream << m_separator << summaryType.c_str();
Chris@498 262 }
Chris@1001 263
Chris@1001 264 for (unsigned int j = 0; j < f.values.size(); ++j) {
Chris@1001 265 stream << m_separator << f.values[j];
Chris@1001 266 }
Chris@1001 267
Chris@1001 268 if (f.label != "") {
Chris@1001 269 stream << m_separator << "\"" << f.label.c_str() << "\"";
Chris@1001 270 }
Chris@1001 271
Chris@1001 272 stream << "\n";
Chris@498 273 }
Chris@498 274
Chris@498 275