comparison runner/JAMSFeatureWriter.cpp @ 171:c1834a31029c

Merge from branch "jams"
author Chris Cannam
date Wed, 15 Oct 2014 16:09:47 +0100
parents 3536342ac088
children 089f1a13963d
comparison
equal deleted inserted replaced
163:f4f770b4356b 171:c1834a31029c
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Sonic Annotator
5 A utility for batch feature extraction from audio files.
6 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
7 Copyright 2007-2014 QMUL.
8
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License as
11 published by the Free Software Foundation; either version 2 of the
12 License, or (at your option) any later version. See the file
13 COPYING included with this distribution for more information.
14 */
15
16 #include "JAMSFeatureWriter.h"
17
18 using namespace std;
19 using Vamp::Plugin;
20 using Vamp::PluginBase;
21
22 #include "base/Exceptions.h"
23 #include "rdf/PluginRDFIndexer.h"
24
25 #include <QFileInfo>
26
27 #include "version.h"
28
29 JAMSFeatureWriter::JAMSFeatureWriter() :
30 FileFeatureWriter(SupportOneFilePerTrackTransform |
31 SupportOneFilePerTrack |
32 SupportOneFileTotal |
33 SupportStdOut,
34 "json"),
35 m_network(false),
36 m_networkRetrieved(false),
37 m_n(1),
38 m_m(1)
39 {
40 }
41
42 JAMSFeatureWriter::~JAMSFeatureWriter()
43 {
44 }
45
46 string
47 JAMSFeatureWriter::getDescription() const
48 {
49 return "Write features to JSON files in JAMS (JSON Annotated Music Specification) format. WARNING: This is a provisional implementation! The output format may change in future releases to comply more effectively with the specification. Please report any problems you find with the current implementation.";
50 }
51
52 JAMSFeatureWriter::ParameterList
53 JAMSFeatureWriter::getSupportedParameters() const
54 {
55 ParameterList pl = FileFeatureWriter::getSupportedParameters();
56 Parameter p;
57
58 p.name = "network";
59 p.description = "Attempt to retrieve RDF descriptions of plugins from network, if not available locally";
60 p.hasArg = false;
61 pl.push_back(p);
62
63 return pl;
64 }
65
66 void
67 JAMSFeatureWriter::setParameters(map<string, string> &params)
68 {
69 FileFeatureWriter::setParameters(params);
70
71 for (map<string, string>::iterator i = params.begin();
72 i != params.end(); ++i) {
73 if (i->first == "network") {
74 m_network = true;
75 }
76 }
77 }
78
79 void
80 JAMSFeatureWriter::setTrackMetadata(QString trackId, TrackMetadata metadata)
81 {
82 m_trackMetadata[trackId] = metadata;
83 }
84
85 static double
86 realTime2Sec(const Vamp::RealTime &r)
87 {
88 return r / Vamp::RealTime(1, 0);
89 }
90
91 void
92 JAMSFeatureWriter::write(QString trackId,
93 const Transform &transform,
94 const Plugin::OutputDescriptor& ,
95 const Plugin::FeatureList& features,
96 std::string /* summaryType */)
97 {
98 QString transformId = transform.getIdentifier();
99
100 QTextStream *sptr = getOutputStream(trackId, transformId);
101 if (!sptr) {
102 throw FailedToOpenOutputStream(trackId, transformId);
103 }
104
105 DataId did(trackId, transform);
106
107 if (m_data.find(did) == m_data.end()) {
108 identifyTask(transform);
109 m_streamTracks[sptr].insert(trackId);
110 m_streamTasks[sptr].insert(m_tasks[transformId]);
111 m_streamData[sptr].insert(did);
112 }
113
114 QString d = m_data[did];
115
116 for (int i = 0; i < int(features.size()); ++i) {
117
118 if (d != "") {
119 d += ",\n";
120 }
121
122 d += " { ";
123
124 Plugin::Feature f(features[i]);
125
126 if (f.hasDuration) {
127 d += QString
128 ("\"start\": { \"value\": %1 }, "
129 "\"end\": { \"value\": %2 }")
130 .arg(realTime2Sec(f.timestamp))
131 .arg(realTime2Sec
132 (f.timestamp +
133 (f.hasDuration ? f.duration : Vamp::RealTime::zeroTime)));
134 } else {
135 d += QString("\"time\": { \"value\": %1 }")
136 .arg(realTime2Sec(f.timestamp));
137 }
138
139 if (f.label != "") {
140 d += QString(", \"label\": { \"value\": \"%2\" }")
141 .arg(f.label.c_str());
142 }
143
144 if (f.values.size() > 0) {
145 d += QString(", \"value\": [ ");
146 for (int j = 0; j < int(f.values.size()); ++j) {
147 if (isnan(f.values[j])) {
148 d += "\"NaN\" ";
149 } else if (isinf(f.values[j])) {
150 d += "\"Inf\" ";
151 } else {
152 d += QString("%1 ").arg(f.values[j]);
153 }
154 }
155 d += "]";
156 }
157
158 d += " }";
159 }
160
161 m_data[did] = d;
162 }
163
164 void
165 JAMSFeatureWriter::setNofM(int n, int m)
166 {
167 if (m_singleFileName != "" || m_stdout) {
168 m_n = n;
169 m_m = m;
170 } else {
171 m_n = 1;
172 m_m = 1;
173 }
174 }
175
176 void
177 JAMSFeatureWriter::finish()
178 {
179 for (FileStreamMap::const_iterator stri = m_streams.begin();
180 stri != m_streams.end(); ++stri) {
181
182 QTextStream *sptr = stri->second;
183 QTextStream &stream = *sptr;
184
185 bool firstInStream = true;
186
187 for (TrackIds::const_iterator tri = m_streamTracks[sptr].begin();
188 tri != m_streamTracks[sptr].end(); ++tri) {
189
190 TrackId trackId = *tri;
191
192 if (firstInStream) {
193 if (m_streamTracks[sptr].size() > 1 || (m_m > 1 && m_n == 1)) {
194 stream << "[\n";
195 }
196 }
197
198 if (!firstInStream || (m_m > 1 && m_n > 1)) {
199 stream << ",\n";
200 }
201
202 stream << "{\n"
203 << QString("\"file_metadata\": {\n"
204 " \"filename\": \"%1\"")
205 .arg(QFileInfo(trackId).fileName());
206
207 if (m_trackMetadata.find(trackId) != m_trackMetadata.end()) {
208 if (m_trackMetadata[trackId].maker != "") {
209 stream << QString(",\n \"artist\": \"%1\"")
210 .arg(m_trackMetadata[trackId].maker);
211 }
212 if (m_trackMetadata[trackId].title != "") {
213 stream << QString(",\n \"title\": \"%1\"")
214 .arg(m_trackMetadata[trackId].title);
215 }
216 }
217
218 stream << "\n},\n";
219
220 bool firstInTrack = true;
221
222 for (Tasks::const_iterator ti = m_streamTasks[sptr].begin();
223 ti != m_streamTasks[sptr].end(); ++ti) {
224
225 Task task = *ti;
226
227 if (!firstInTrack) {
228 stream << ",\n";
229 }
230
231 stream << "\"" << getTaskKey(task) << "\": [\n";
232
233 bool firstInTask = true;
234
235 for (DataIds::const_iterator di = m_streamData[sptr].begin();
236 di != m_streamData[sptr].end(); ++di) {
237
238 DataId did = *di;
239
240 QString trackId = did.first;
241 Transform transform = did.second;
242
243 if (m_tasks[transform.getIdentifier()] != task) continue;
244
245 QString data = m_data[did];
246
247 if (!firstInTask) {
248 stream << ",\n";
249 }
250
251 stream << QString
252 ("{ \n"
253 " \"annotation_metadata\": {\n"
254 " \"annotation_tools\": \"Sonic Annotator v%2\",\n"
255 " \"data_source\": \"Automatic feature extraction\",\n"
256 " \"annotator\": {\n"
257 "%3"
258 " }\n"
259 " },\n"
260 " \"data\": [\n")
261 .arg(RUNNER_VERSION)
262 .arg(writeTransformToObjectContents(transform));
263
264 stream << data;
265
266 stream << "\n ]\n}";
267 firstInTask = false;
268 }
269
270 stream << "\n]";
271 firstInTrack = false;
272 }
273
274 stream << "\n}";
275 firstInStream = false;
276 }
277
278 if (!firstInStream) {
279 if (m_streamTracks[sptr].size() > 1 || (m_m > 1 && m_n == m_m)) {
280 stream << "\n]";
281 }
282 stream << "\n";
283 }
284 }
285
286 m_streamTracks.clear();
287 m_streamTasks.clear();
288 m_streamData.clear();
289 m_data.clear();
290
291 FileFeatureWriter::finish();
292 }
293
294 void
295 JAMSFeatureWriter::loadRDFDescription(const Transform &transform)
296 {
297 QString pluginId = transform.getPluginIdentifier();
298 if (m_rdfDescriptions.find(pluginId) != m_rdfDescriptions.end()) return;
299
300 if (m_network && !m_networkRetrieved) {
301 PluginRDFIndexer::getInstance()->indexConfiguredURLs();
302 m_networkRetrieved = true;
303 }
304
305 m_rdfDescriptions[pluginId] = PluginRDFDescription(pluginId);
306
307 if (m_rdfDescriptions[pluginId].haveDescription()) {
308 cerr << "NOTE: Have RDF description for plugin ID \""
309 << pluginId << "\"" << endl;
310 } else {
311 cerr << "NOTE: No RDF description for plugin ID \""
312 << pluginId << "\"" << endl;
313 if (!m_network) {
314 cerr << " Consider using the --json-network option to retrieve plugin descriptions" << endl;
315 cerr << " from the network where possible." << endl;
316 }
317 }
318 }
319
320 void
321 JAMSFeatureWriter::identifyTask(const Transform &transform)
322 {
323 QString transformId = transform.getIdentifier();
324 if (m_tasks.find(transformId) != m_tasks.end()) return;
325
326 loadRDFDescription(transform);
327
328 Task task = UnknownTask;
329
330 QString pluginId = transform.getPluginIdentifier();
331 QString outputId = transform.getOutput();
332
333 const PluginRDFDescription &desc = m_rdfDescriptions[pluginId];
334
335 if (desc.haveDescription()) {
336
337 PluginRDFDescription::OutputDisposition disp =
338 desc.getOutputDisposition(outputId);
339
340 QString af = "http://purl.org/ontology/af/";
341
342 if (disp == PluginRDFDescription::OutputSparse) {
343
344 QString eventUri = desc.getOutputEventTypeURI(outputId);
345
346 //!!! todo: allow user to prod writer for task type
347
348 if (eventUri == af + "Note") {
349 task = NoteTask;
350 } else if (eventUri == af + "Beat") {
351 task = BeatTask;
352 } else if (eventUri == af + "ChordSegment") {
353 task = ChordTask;
354 } else if (eventUri == af + "KeyChange") {
355 task = KeyTask;
356 } else if (eventUri == af + "KeySegment") {
357 task = KeyTask;
358 } else if (eventUri == af + "Onset") {
359 task = OnsetTask;
360 } else if (eventUri == af + "NonTonalOnset") {
361 task = OnsetTask;
362 } else if (eventUri == af + "Segment") {
363 task = SegmentTask;
364 } else if (eventUri == af + "SpeechSegment") {
365 task = SegmentTask;
366 } else if (eventUri == af + "StructuralSegment") {
367 task = SegmentTask;
368 } else {
369 cerr << "WARNING: Unsupported event type URI <"
370 << eventUri << ">, proceeding with UnknownTask type"
371 << endl;
372 }
373
374 } else {
375
376 cerr << "WARNING: Cannot currently write dense or track-level outputs to JSON format (only sparse ones). Will proceed using UnknownTask type, but this probably isn't going to work" << endl;
377 }
378 }
379
380 m_tasks[transformId] = task;
381 }
382
383 QString
384 JAMSFeatureWriter::getTaskKey(Task task)
385 {
386 switch (task) {
387 case UnknownTask: return "unknown";
388 case BeatTask: return "beat";
389 case OnsetTask: return "onset";
390 case ChordTask: return "chord";
391 case SegmentTask: return "segment";
392 case KeyTask: return "key";
393 case NoteTask: return "note";
394 case MelodyTask: return "melody";
395 case PitchTask: return "pitch";
396 }
397 return "unknown";
398 }
399
400 QString
401 JAMSFeatureWriter::writeTransformToObjectContents(const Transform &t)
402 {
403 QString json;
404 QString stpl(" \"%1\": \"%2\",\n");
405 QString ntpl(" \"%1\": %2,\n");
406
407 json += stpl.arg("plugin_id").arg(t.getPluginIdentifier());
408 json += stpl.arg("output_id").arg(t.getOutput());
409
410 if (t.getSummaryType() != Transform::NoSummary) {
411 json += stpl.arg("summary_type")
412 .arg(Transform::summaryTypeToString(t.getSummaryType()));
413 }
414
415 if (t.getPluginVersion() != QString()) {
416 json += stpl.arg("plugin_version").arg(t.getPluginVersion());
417 }
418
419 if (t.getProgram() != QString()) {
420 json += stpl.arg("program").arg(t.getProgram());
421 }
422
423 if (t.getStepSize() != 0) {
424 json += ntpl.arg("step_size").arg(t.getStepSize());
425 }
426
427 if (t.getBlockSize() != 0) {
428 json += ntpl.arg("block_size").arg(t.getBlockSize());
429 }
430
431 if (t.getWindowType() != HanningWindow) {
432 json += stpl.arg("window_type")
433 .arg(Window<float>::getNameForType(t.getWindowType()).c_str());
434 }
435
436 if (t.getStartTime() != RealTime::zeroTime) {
437 json += ntpl.arg("start").arg(t.getStartTime().toDouble());
438 }
439
440 if (t.getDuration() != RealTime::zeroTime) {
441 json += ntpl.arg("duration").arg(t.getDuration().toDouble());
442 }
443
444 if (t.getSampleRate() != 0) {
445 json += ntpl.arg("sample_rate").arg(t.getSampleRate());
446 }
447
448 if (!t.getParameters().empty()) {
449 json += QString(" \"parameters\": {\n");
450 Transform::ParameterMap parameters = t.getParameters();
451 for (Transform::ParameterMap::const_iterator i = parameters.begin();
452 i != parameters.end(); ++i) {
453 if (i != parameters.begin()) {
454 json += ",\n";
455 }
456 QString name = i->first;
457 float value = i->second;
458 json += QString(" \"%1\": %2").arg(name).arg(value);
459 }
460 json += QString("\n },\n");
461 }
462
463 // no trailing comma on final property:
464 json += QString(" \"transform_id\": \"%1\"\n").arg(t.getIdentifier());
465
466 return json;
467 }
468