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> ¶ms)
|
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@1039
|
136 int n = (int)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@1047
|
213 ::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@1047
|
226 sv_samplerate_t rate = transform.getSampleRate();
|
Chris@1001
|
227
|
Chris@1047
|
228 stream << ::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@1047
|
233 stream << ::RealTime::realTime2Frame
|
Chris@1047
|
234 (::RealTime(f.timestamp) + duration, rate);
|
Chris@514
|
235 } else {
|
Chris@1047
|
236 stream << ::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@1047
|
249 (::RealTime(f.timestamp) + duration).toString().c_str();
|
Chris@1001
|
250 endtime.replace(QRegExp("^ +"), "");
|
Chris@1001
|
251 stream << m_separator << endtime;
|
Chris@1001
|
252 } else {
|
Chris@1047
|
253 QString d = ::RealTime(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
|