annotate runner/JAMSFeatureWriter.cpp @ 152:db83ea0e102d jams

Make the JSON well-formed (though still empty), except in the case where we are asked to write more than one file's features to the same output file or stdout
author Chris Cannam
date Tue, 14 Oct 2014 12:35:19 +0100
parents 3921e0c1f4dd
children ad96fd5f9cd7
rev   line source
Chris@145 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@145 2
Chris@145 3 /*
Chris@145 4 Sonic Annotator
Chris@145 5 A utility for batch feature extraction from audio files.
Chris@145 6 Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London.
Chris@145 7 Copyright 2007-2014 QMUL.
Chris@145 8
Chris@145 9 This program is free software; you can redistribute it and/or
Chris@145 10 modify it under the terms of the GNU General Public License as
Chris@145 11 published by the Free Software Foundation; either version 2 of the
Chris@145 12 License, or (at your option) any later version. See the file
Chris@145 13 COPYING included with this distribution for more information.
Chris@145 14 */
Chris@145 15
Chris@145 16 #include "JAMSFeatureWriter.h"
Chris@145 17
Chris@145 18 using namespace std;
Chris@145 19 using Vamp::Plugin;
Chris@145 20 using Vamp::PluginBase;
Chris@145 21
Chris@145 22 #include "base/Exceptions.h"
Chris@145 23 #include "rdf/PluginRDFIndexer.h"
Chris@145 24
Chris@145 25 JAMSFeatureWriter::JAMSFeatureWriter() :
Chris@145 26 FileFeatureWriter(SupportOneFilePerTrackTransform |
Chris@145 27 SupportOneFilePerTrack |
Chris@152 28 SupportOneFileTotal |
Chris@145 29 SupportStdOut,
Chris@145 30 "json"),
Chris@145 31 m_network(false),
Chris@145 32 m_networkRetrieved(false)
Chris@145 33 {
Chris@145 34 }
Chris@145 35
Chris@145 36 JAMSFeatureWriter::~JAMSFeatureWriter()
Chris@145 37 {
Chris@145 38 }
Chris@145 39
Chris@145 40 string
Chris@145 41 JAMSFeatureWriter::getDescription() const
Chris@145 42 {
Chris@145 43 return "Write features to JSON files in JAMS (JSON Annotated Music Specification) format.";
Chris@145 44 }
Chris@145 45
Chris@145 46 JAMSFeatureWriter::ParameterList
Chris@145 47 JAMSFeatureWriter::getSupportedParameters() const
Chris@145 48 {
Chris@145 49 ParameterList pl = FileFeatureWriter::getSupportedParameters();
Chris@145 50 Parameter p;
Chris@145 51
Chris@145 52 p.name = "network";
Chris@145 53 p.description = "Attempt to retrieve RDF descriptions of plugins from network, if not available locally";
Chris@145 54 p.hasArg = false;
Chris@145 55 pl.push_back(p);
Chris@145 56
Chris@145 57 return pl;
Chris@145 58 }
Chris@145 59
Chris@145 60 void
Chris@145 61 JAMSFeatureWriter::setParameters(map<string, string> &params)
Chris@145 62 {
Chris@145 63 FileFeatureWriter::setParameters(params);
Chris@145 64
Chris@145 65 for (map<string, string>::iterator i = params.begin();
Chris@145 66 i != params.end(); ++i) {
Chris@145 67 if (i->first == "network") {
Chris@145 68 m_network = true;
Chris@145 69 }
Chris@145 70 }
Chris@145 71 }
Chris@145 72
Chris@145 73 void
Chris@145 74 JAMSFeatureWriter::setTrackMetadata(QString trackId, TrackMetadata metadata)
Chris@145 75 {
Chris@145 76 QString json
Chris@152 77 (" \"file_metadata\":\n"
Chris@152 78 " { \"artist\": \"%1\",\n"
Chris@152 79 " \"title\": \"%2\" },\n");
Chris@145 80 m_metadata[trackId] = json.arg(metadata.maker).arg(metadata.title);
Chris@152 81 cerr << "setTrackMetadata: metadata is: " << m_metadata[trackId] << endl;
Chris@145 82 }
Chris@145 83
Chris@145 84 void
Chris@145 85 JAMSFeatureWriter::write(QString trackId,
Chris@145 86 const Transform &transform,
Chris@145 87 const Plugin::OutputDescriptor& ,
Chris@145 88 const Plugin::FeatureList& features,
Chris@145 89 std::string /* summaryType */)
Chris@145 90 {
Chris@145 91 QString transformId = transform.getIdentifier();
Chris@145 92
Chris@145 93 QTextStream *sptr = getOutputStream(trackId, transformId);
Chris@145 94 if (!sptr) {
Chris@145 95 throw FailedToOpenOutputStream(trackId, transformId);
Chris@145 96 }
Chris@145 97
Chris@145 98 QTextStream &stream = *sptr;
Chris@145 99
Chris@152 100 TrackTransformPair tt(trackId, transformId);
Chris@152 101 TrackTransformPair targetKey = getFilenameKey(trackId, transformId);
Chris@152 102
Chris@152 103 if (m_startedTargets.find(targetKey) == m_startedTargets.end()) {
Chris@152 104 // Need to write track-level preamble
Chris@152 105 stream << "{" << m_metadata[trackId] << endl;
Chris@152 106 m_startedTargets.insert(targetKey);
Chris@152 107 }
Chris@152 108
Chris@152 109 if (m_data.find(tt) == m_data.end()) {
Chris@145 110
Chris@145 111 identifyTask(transform);
Chris@145 112
Chris@152 113 QString json("\"%1\": [ ");
Chris@152 114 m_data[tt] = json.arg(getTaskKey(m_tasks[transformId]));
Chris@145 115 }
Chris@145 116
Chris@145 117 for (int i = 0; i < int(features.size()); ++i) {
Chris@145 118
Chris@145 119 }
Chris@145 120 }
Chris@145 121
Chris@145 122 void
Chris@152 123 JAMSFeatureWriter::finish()
Chris@152 124 {
Chris@152 125 cerr << "Finish called on " << this << endl;
Chris@152 126
Chris@152 127 set<QTextStream *> startedStreams;
Chris@152 128
Chris@152 129 for (DataMap::const_iterator i = m_data.begin();
Chris@152 130 i != m_data.end(); ++i) {
Chris@152 131
Chris@152 132 TrackTransformPair tt = i->first;
Chris@152 133 QString data = i->second;
Chris@152 134
Chris@152 135 QTextStream *sptr = getOutputStream(tt.first, tt.second);
Chris@152 136 if (!sptr) {
Chris@152 137 throw FailedToOpenOutputStream(tt.first, tt.second);
Chris@152 138 }
Chris@152 139
Chris@152 140 if (startedStreams.find(sptr) != startedStreams.end()) {
Chris@152 141 *sptr << "," << endl;
Chris@152 142 }
Chris@152 143 startedStreams.insert(sptr);
Chris@152 144
Chris@152 145 *sptr << data << "]";
Chris@152 146 }
Chris@152 147
Chris@152 148 for (FileStreamMap::const_iterator i = m_streams.begin();
Chris@152 149 i != m_streams.end(); ++i) {
Chris@152 150 *(i->second) << endl << "}" << endl;
Chris@152 151 }
Chris@152 152
Chris@152 153 m_data.clear();
Chris@152 154 m_startedTargets.clear();
Chris@152 155
Chris@152 156 FileFeatureWriter::finish();
Chris@152 157 }
Chris@152 158
Chris@152 159 void
Chris@145 160 JAMSFeatureWriter::loadRDFDescription(const Transform &transform)
Chris@145 161 {
Chris@145 162 QString pluginId = transform.getPluginIdentifier();
Chris@145 163 if (m_rdfDescriptions.find(pluginId) != m_rdfDescriptions.end()) return;
Chris@145 164
Chris@145 165 if (m_network && !m_networkRetrieved) {
Chris@145 166 PluginRDFIndexer::getInstance()->indexConfiguredURLs();
Chris@145 167 m_networkRetrieved = true;
Chris@145 168 }
Chris@145 169
Chris@145 170 m_rdfDescriptions[pluginId] = PluginRDFDescription(pluginId);
Chris@145 171
Chris@145 172 if (m_rdfDescriptions[pluginId].haveDescription()) {
Chris@145 173 cerr << "NOTE: Have RDF description for plugin ID \""
Chris@145 174 << pluginId << "\"" << endl;
Chris@145 175 } else {
Chris@145 176 cerr << "NOTE: No RDF description for plugin ID \""
Chris@145 177 << pluginId << "\"" << endl;
Chris@145 178 if (!m_network) {
Chris@145 179 cerr << " Consider using the --json-network option to retrieve plugin descriptions" << endl;
Chris@145 180 cerr << " from the network where possible." << endl;
Chris@145 181 }
Chris@145 182 }
Chris@145 183 }
Chris@145 184
Chris@145 185 void
Chris@145 186 JAMSFeatureWriter::identifyTask(const Transform &transform)
Chris@145 187 {
Chris@145 188 QString transformId = transform.getIdentifier();
Chris@145 189 if (m_tasks.find(transformId) != m_tasks.end()) return;
Chris@145 190
Chris@145 191 loadRDFDescription(transform);
Chris@145 192
Chris@145 193 Task task = UnknownTask;
Chris@145 194
Chris@145 195 QString pluginId = transform.getPluginIdentifier();
Chris@145 196 QString outputId = transform.getOutput();
Chris@145 197
Chris@145 198 const PluginRDFDescription &desc = m_rdfDescriptions[pluginId];
Chris@145 199
Chris@145 200 if (desc.haveDescription()) {
Chris@145 201
Chris@145 202 PluginRDFDescription::OutputDisposition disp =
Chris@145 203 desc.getOutputDisposition(outputId);
Chris@145 204
Chris@145 205 QString af = "http://purl.org/ontology/af/";
Chris@145 206
Chris@145 207 if (disp == PluginRDFDescription::OutputSparse) {
Chris@145 208
Chris@145 209 QString eventUri = desc.getOutputEventTypeURI(outputId);
Chris@145 210
Chris@145 211 //!!! todo: allow user to prod writer for task type
Chris@145 212
Chris@145 213 if (eventUri == af + "Note") {
Chris@145 214 task = NoteTask;
Chris@145 215 } else if (eventUri == af + "Beat") {
Chris@145 216 task = BeatTask;
Chris@145 217 } else if (eventUri == af + "ChordSegment") {
Chris@145 218 task = ChordTask;
Chris@145 219 } else if (eventUri == af + "KeyChange") {
Chris@145 220 task = KeyTask;
Chris@145 221 } else if (eventUri == af + "KeySegment") {
Chris@145 222 task = KeyTask;
Chris@145 223 } else if (eventUri == af + "Onset") {
Chris@145 224 task = OnsetTask;
Chris@145 225 } else if (eventUri == af + "NonTonalOnset") {
Chris@145 226 task = OnsetTask;
Chris@145 227 } else if (eventUri == af + "Segment") {
Chris@145 228 task = SegmentTask;
Chris@145 229 } else if (eventUri == af + "SpeechSegment") {
Chris@145 230 task = SegmentTask;
Chris@145 231 } else if (eventUri == af + "StructuralSegment") {
Chris@145 232 task = SegmentTask;
Chris@145 233 } else {
Chris@145 234 cerr << "WARNING: Unsupported event type URI <"
Chris@145 235 << eventUri << ">, proceeding with UnknownTask type"
Chris@145 236 << endl;
Chris@145 237 }
Chris@145 238
Chris@145 239 } else {
Chris@145 240
Chris@145 241 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;
Chris@145 242 }
Chris@145 243 }
Chris@145 244
Chris@145 245 m_tasks[transformId] = task;
Chris@145 246 }
Chris@145 247
Chris@145 248 QString
Chris@145 249 JAMSFeatureWriter::getTaskKey(Task task)
Chris@145 250 {
Chris@145 251 switch (task) {
Chris@145 252 case UnknownTask: return "unknown";
Chris@145 253 case BeatTask: return "beat";
Chris@145 254 case OnsetTask: return "onset";
Chris@145 255 case ChordTask: return "chord";
Chris@145 256 case SegmentTask: return "segment";
Chris@145 257 case KeyTask: return "key";
Chris@145 258 case NoteTask: return "note";
Chris@145 259 case MelodyTask: return "melody";
Chris@145 260 case PitchTask: return "pitch";
Chris@145 261 }
Chris@145 262 return "unknown";
Chris@145 263 }