comparison transform/CSVFeatureWriter.cpp @ 1001:51bf067de517

Add fill-ends option to CSV writer (and test it)
author Chris Cannam
date Wed, 15 Oct 2014 10:18:13 +0100
parents ec6e69373997
children c2316a3bbb81
comparison
equal deleted inserted replaced
1000:ec6e69373997 1001:51bf067de517
32 SupportOneFileTotal | 32 SupportOneFileTotal |
33 SupportStdOut, 33 SupportStdOut,
34 "csv"), 34 "csv"),
35 m_separator(","), 35 m_separator(","),
36 m_sampleTiming(false), 36 m_sampleTiming(false),
37 m_endTimes(false) 37 m_endTimes(false),
38 m_forceEnd(false)
38 { 39 {
39 } 40 }
40 41
41 CSVFeatureWriter::~CSVFeatureWriter() 42 CSVFeatureWriter::~CSVFeatureWriter()
42 { 43 {
64 p.hasArg = false; 65 p.hasArg = false;
65 pl.push_back(p); 66 pl.push_back(p);
66 67
67 p.name = "end-times"; 68 p.name = "end-times";
68 p.description = "Show start and end time instead of start and duration, for features with duration."; 69 p.description = "Show start and end time instead of start and duration, for features with duration.";
70 p.hasArg = false;
71 pl.push_back(p);
72
73 p.name = "fill-ends";
74 p.description = "Include durations (or end times) even for features without duration, by using the gap to the next feature instead.";
69 p.hasArg = false; 75 p.hasArg = false;
70 pl.push_back(p); 76 pl.push_back(p);
71 77
72 return pl; 78 return pl;
73 } 79 }
85 m_separator = i->second.c_str(); 91 m_separator = i->second.c_str();
86 } else if (i->first == "sample-timing") { 92 } else if (i->first == "sample-timing") {
87 m_sampleTiming = true; 93 m_sampleTiming = true;
88 } else if (i->first == "end-times") { 94 } else if (i->first == "end-times") {
89 m_endTimes = true; 95 m_endTimes = true;
96 } else if (i->first == "fill-ends") {
97 m_forceEnd = true;
90 } 98 }
91 } 99 }
92 } 100 }
93 101
94 void 102 void
96 const Transform &transform, 104 const Transform &transform,
97 const Plugin::OutputDescriptor& , 105 const Plugin::OutputDescriptor& ,
98 const Plugin::FeatureList& features, 106 const Plugin::FeatureList& features,
99 std::string summaryType) 107 std::string summaryType)
100 { 108 {
109 TransformId transformId = transform.getIdentifier();
110
101 // Select appropriate output file for our track/transform 111 // Select appropriate output file for our track/transform
102 // combination 112 // combination
103 113
104 QTextStream *sptr = getOutputStream(trackId, transform.getIdentifier()); 114 QTextStream *sptr = getOutputStream(trackId, transformId);
105 if (!sptr) { 115 if (!sptr) {
106 throw FailedToOpenOutputStream(trackId, transform.getIdentifier()); 116 throw FailedToOpenOutputStream(trackId, transformId);
107 } 117 }
108 118
109 QTextStream &stream = *sptr; 119 QTextStream &stream = *sptr;
110 120
111 for (unsigned int i = 0; i < features.size(); ++i) { 121 int n = features.size();
112 122
113 if (m_stdout || m_singleFileName != "") { 123 if (n == 0) return;
114 if (trackId != m_prevPrintedTrackId) { 124
115 stream << "\"" << trackId << "\"" << m_separator; 125 TrackTransformPair tt(trackId, transformId);
116 m_prevPrintedTrackId = trackId; 126
127 if (m_rates.find(transformId) == m_rates.end()) {
128 m_rates[transformId] = transform.getSampleRate();
129 }
130
131 if (m_pending.find(tt) != m_pending.end()) {
132 writeFeature(tt,
133 stream,
134 m_pending[tt],
135 &features[0],
136 m_pendingSummaryTypes[tt]);
137 m_pending.erase(tt);
138 m_pendingSummaryTypes.erase(tt);
139 }
140
141 if (m_forceEnd) {
142 // can't write final feature until we know its end time
143 --n;
144 m_pending[tt] = features[n];
145 m_pendingSummaryTypes[tt] = summaryType;
146 }
147
148 for (int i = 0; i < n; ++i) {
149 writeFeature(tt,
150 stream,
151 features[i],
152 m_forceEnd ? &features[i+1] : 0,
153 summaryType);
154 }
155 }
156
157 void
158 CSVFeatureWriter::finish()
159 {
160 for (PendingFeatures::const_iterator i = m_pending.begin();
161 i != m_pending.end(); ++i) {
162 TrackTransformPair tt = i->first;
163 Plugin::Feature f = i->second;
164 QTextStream *sptr = getOutputStream(tt.first, tt.second);
165 if (!sptr) {
166 throw FailedToOpenOutputStream(tt.first, tt.second);
167 }
168 QTextStream &stream = *sptr;
169 // final feature has its own time as end time (we can't
170 // reliably determine the end of audio file, and because of
171 // the nature of block processing, the feature could even
172 // start beyond that anyway)
173 writeFeature(tt, stream, f, &f, m_pendingSummaryTypes[tt]);
174 }
175
176 m_pending.clear();
177 }
178
179 void
180 CSVFeatureWriter::writeFeature(TrackTransformPair tt,
181 QTextStream &stream,
182 const Plugin::Feature &f,
183 const Plugin::Feature *optionalNextFeature,
184 std::string summaryType)
185 {
186 QString trackId = tt.first;
187 TransformId transformId = tt.second;
188
189 if (m_stdout || m_singleFileName != "") {
190 if (trackId != m_prevPrintedTrackId) {
191 stream << "\"" << trackId << "\"" << m_separator;
192 m_prevPrintedTrackId = trackId;
193 } else {
194 stream << m_separator;
195 }
196 }
197
198 Vamp::RealTime duration;
199 bool haveDuration = true;
200
201 if (f.hasDuration) {
202 duration = f.duration;
203 } else if (optionalNextFeature) {
204 duration = optionalNextFeature->timestamp - f.timestamp;
205 } else {
206 haveDuration = false;
207 }
208
209 if (m_sampleTiming) {
210
211 float rate = m_rates[transformId];
212
213 stream << Vamp::RealTime::realTime2Frame(f.timestamp, rate);
214
215 if (haveDuration) {
216 stream << m_separator;
217 if (m_endTimes) {
218 stream << Vamp::RealTime::realTime2Frame
219 (f.timestamp + duration, rate);
117 } else { 220 } else {
118 stream << m_separator; 221 stream << Vamp::RealTime::realTime2Frame(duration, rate);
119 } 222 }
120 } 223 }
121 224
122 if (m_sampleTiming) { 225 } else {
123 226
124 stream << Vamp::RealTime::realTime2Frame 227 QString timestamp = f.timestamp.toString().c_str();
125 (features[i].timestamp, transform.getSampleRate()); 228 timestamp.replace(QRegExp("^ +"), "");
126 229 stream << timestamp;
127 if (features[i].hasDuration) { 230
128 stream << m_separator; 231 if (haveDuration) {
129 if (m_endTimes) { 232 if (m_endTimes) {
130 stream << Vamp::RealTime::realTime2Frame 233 QString endtime =
131 (features[i].timestamp + features[i].duration, 234 (f.timestamp + duration).toString().c_str();
132 transform.getSampleRate()); 235 endtime.replace(QRegExp("^ +"), "");
133 } else { 236 stream << m_separator << endtime;
134 stream << Vamp::RealTime::realTime2Frame 237 } else {
135 (features[i].duration, transform.getSampleRate()); 238 QString d = duration.toString().c_str();
136 } 239 d.replace(QRegExp("^ +"), "");
240 stream << m_separator << d;
137 } 241 }
138 242 }
139 } else { 243 }
140 244
141 QString timestamp = features[i].timestamp.toString().c_str(); 245 if (summaryType != "") {
142 timestamp.replace(QRegExp("^ +"), ""); 246 stream << m_separator << summaryType.c_str();
143 stream << timestamp; 247 }
144 248
145 if (features[i].hasDuration) { 249 for (unsigned int j = 0; j < f.values.size(); ++j) {
146 if (m_endTimes) { 250 stream << m_separator << f.values[j];
147 QString endtime = 251 }
148 (features[i].timestamp + features[i].duration) 252
149 .toString().c_str(); 253 if (f.label != "") {
150 endtime.replace(QRegExp("^ +"), ""); 254 stream << m_separator << "\"" << f.label.c_str() << "\"";
151 stream << m_separator << endtime; 255 }
152 } else { 256
153 QString duration = features[i].duration.toString().c_str(); 257 stream << "\n";
154 duration.replace(QRegExp("^ +"), ""); 258 }
155 stream << m_separator << duration; 259
156 } 260
157 }
158 }
159
160 if (summaryType != "") {
161 stream << m_separator << summaryType.c_str();
162 }
163
164 for (unsigned int j = 0; j < features[i].values.size(); ++j) {
165 stream << m_separator << features[i].values[j];
166 }
167
168 if (features[i].label != "") {
169 stream << m_separator << "\"" << features[i].label.c_str() << "\"";
170 }
171
172 stream << "\n";
173 }
174 }
175
176