Chris@145: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@145: Chris@145: /* Chris@145: Sonic Annotator Chris@145: A utility for batch feature extraction from audio files. Chris@145: Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London. Chris@145: Copyright 2007-2014 QMUL. Chris@145: Chris@145: This program is free software; you can redistribute it and/or Chris@145: modify it under the terms of the GNU General Public License as Chris@145: published by the Free Software Foundation; either version 2 of the Chris@145: License, or (at your option) any later version. See the file Chris@145: COPYING included with this distribution for more information. Chris@145: */ Chris@145: Chris@145: #include "JAMSFeatureWriter.h" Chris@145: Chris@145: using namespace std; Chris@145: using Vamp::Plugin; Chris@145: using Vamp::PluginBase; Chris@145: Chris@145: #include "base/Exceptions.h" Chris@145: #include "rdf/PluginRDFIndexer.h" Chris@145: Chris@145: JAMSFeatureWriter::JAMSFeatureWriter() : Chris@145: FileFeatureWriter(SupportOneFilePerTrackTransform | Chris@145: SupportOneFilePerTrack | Chris@152: SupportOneFileTotal | Chris@145: SupportStdOut, Chris@145: "json"), Chris@145: m_network(false), Chris@145: m_networkRetrieved(false) Chris@145: { Chris@145: } Chris@145: Chris@145: JAMSFeatureWriter::~JAMSFeatureWriter() Chris@145: { Chris@145: } Chris@145: Chris@145: string Chris@145: JAMSFeatureWriter::getDescription() const Chris@145: { Chris@145: return "Write features to JSON files in JAMS (JSON Annotated Music Specification) format."; Chris@145: } Chris@145: Chris@145: JAMSFeatureWriter::ParameterList Chris@145: JAMSFeatureWriter::getSupportedParameters() const Chris@145: { Chris@145: ParameterList pl = FileFeatureWriter::getSupportedParameters(); Chris@145: Parameter p; Chris@145: Chris@145: p.name = "network"; Chris@145: p.description = "Attempt to retrieve RDF descriptions of plugins from network, if not available locally"; Chris@145: p.hasArg = false; Chris@145: pl.push_back(p); Chris@145: Chris@145: return pl; Chris@145: } Chris@145: Chris@145: void Chris@145: JAMSFeatureWriter::setParameters(map ¶ms) Chris@145: { Chris@145: FileFeatureWriter::setParameters(params); Chris@145: Chris@145: for (map::iterator i = params.begin(); Chris@145: i != params.end(); ++i) { Chris@145: if (i->first == "network") { Chris@145: m_network = true; Chris@145: } Chris@145: } Chris@145: } Chris@145: Chris@145: void Chris@145: JAMSFeatureWriter::setTrackMetadata(QString trackId, TrackMetadata metadata) Chris@145: { Chris@145: QString json Chris@152: (" \"file_metadata\":\n" Chris@152: " { \"artist\": \"%1\",\n" Chris@152: " \"title\": \"%2\" },\n"); Chris@145: m_metadata[trackId] = json.arg(metadata.maker).arg(metadata.title); Chris@152: cerr << "setTrackMetadata: metadata is: " << m_metadata[trackId] << endl; Chris@145: } Chris@145: Chris@145: void Chris@145: JAMSFeatureWriter::write(QString trackId, Chris@145: const Transform &transform, Chris@145: const Plugin::OutputDescriptor& , Chris@145: const Plugin::FeatureList& features, Chris@145: std::string /* summaryType */) Chris@145: { Chris@145: QString transformId = transform.getIdentifier(); Chris@145: Chris@145: QTextStream *sptr = getOutputStream(trackId, transformId); Chris@145: if (!sptr) { Chris@145: throw FailedToOpenOutputStream(trackId, transformId); Chris@145: } Chris@145: Chris@145: QTextStream &stream = *sptr; Chris@145: Chris@152: TrackTransformPair tt(trackId, transformId); Chris@152: TrackTransformPair targetKey = getFilenameKey(trackId, transformId); Chris@152: Chris@152: if (m_startedTargets.find(targetKey) == m_startedTargets.end()) { Chris@152: // Need to write track-level preamble Chris@152: stream << "{" << m_metadata[trackId] << endl; Chris@152: m_startedTargets.insert(targetKey); Chris@152: } Chris@152: Chris@152: if (m_data.find(tt) == m_data.end()) { Chris@145: Chris@145: identifyTask(transform); Chris@145: Chris@152: QString json("\"%1\": [ "); Chris@152: m_data[tt] = json.arg(getTaskKey(m_tasks[transformId])); Chris@145: } Chris@145: Chris@145: for (int i = 0; i < int(features.size()); ++i) { Chris@145: Chris@145: } Chris@145: } Chris@145: Chris@145: void Chris@152: JAMSFeatureWriter::finish() Chris@152: { Chris@152: cerr << "Finish called on " << this << endl; Chris@152: Chris@152: set startedStreams; Chris@152: Chris@152: for (DataMap::const_iterator i = m_data.begin(); Chris@152: i != m_data.end(); ++i) { Chris@152: Chris@152: TrackTransformPair tt = i->first; Chris@152: QString data = i->second; Chris@152: Chris@152: QTextStream *sptr = getOutputStream(tt.first, tt.second); Chris@152: if (!sptr) { Chris@152: throw FailedToOpenOutputStream(tt.first, tt.second); Chris@152: } Chris@152: Chris@152: if (startedStreams.find(sptr) != startedStreams.end()) { Chris@152: *sptr << "," << endl; Chris@152: } Chris@152: startedStreams.insert(sptr); Chris@152: Chris@152: *sptr << data << "]"; Chris@152: } Chris@152: Chris@152: for (FileStreamMap::const_iterator i = m_streams.begin(); Chris@152: i != m_streams.end(); ++i) { Chris@152: *(i->second) << endl << "}" << endl; Chris@152: } Chris@152: Chris@152: m_data.clear(); Chris@152: m_startedTargets.clear(); Chris@152: Chris@152: FileFeatureWriter::finish(); Chris@152: } Chris@152: Chris@152: void Chris@145: JAMSFeatureWriter::loadRDFDescription(const Transform &transform) Chris@145: { Chris@145: QString pluginId = transform.getPluginIdentifier(); Chris@145: if (m_rdfDescriptions.find(pluginId) != m_rdfDescriptions.end()) return; Chris@145: Chris@145: if (m_network && !m_networkRetrieved) { Chris@145: PluginRDFIndexer::getInstance()->indexConfiguredURLs(); Chris@145: m_networkRetrieved = true; Chris@145: } Chris@145: Chris@145: m_rdfDescriptions[pluginId] = PluginRDFDescription(pluginId); Chris@145: Chris@145: if (m_rdfDescriptions[pluginId].haveDescription()) { Chris@145: cerr << "NOTE: Have RDF description for plugin ID \"" Chris@145: << pluginId << "\"" << endl; Chris@145: } else { Chris@145: cerr << "NOTE: No RDF description for plugin ID \"" Chris@145: << pluginId << "\"" << endl; Chris@145: if (!m_network) { Chris@145: cerr << " Consider using the --json-network option to retrieve plugin descriptions" << endl; Chris@145: cerr << " from the network where possible." << endl; Chris@145: } Chris@145: } Chris@145: } Chris@145: Chris@145: void Chris@145: JAMSFeatureWriter::identifyTask(const Transform &transform) Chris@145: { Chris@145: QString transformId = transform.getIdentifier(); Chris@145: if (m_tasks.find(transformId) != m_tasks.end()) return; Chris@145: Chris@145: loadRDFDescription(transform); Chris@145: Chris@145: Task task = UnknownTask; Chris@145: Chris@145: QString pluginId = transform.getPluginIdentifier(); Chris@145: QString outputId = transform.getOutput(); Chris@145: Chris@145: const PluginRDFDescription &desc = m_rdfDescriptions[pluginId]; Chris@145: Chris@145: if (desc.haveDescription()) { Chris@145: Chris@145: PluginRDFDescription::OutputDisposition disp = Chris@145: desc.getOutputDisposition(outputId); Chris@145: Chris@145: QString af = "http://purl.org/ontology/af/"; Chris@145: Chris@145: if (disp == PluginRDFDescription::OutputSparse) { Chris@145: Chris@145: QString eventUri = desc.getOutputEventTypeURI(outputId); Chris@145: Chris@145: //!!! todo: allow user to prod writer for task type Chris@145: Chris@145: if (eventUri == af + "Note") { Chris@145: task = NoteTask; Chris@145: } else if (eventUri == af + "Beat") { Chris@145: task = BeatTask; Chris@145: } else if (eventUri == af + "ChordSegment") { Chris@145: task = ChordTask; Chris@145: } else if (eventUri == af + "KeyChange") { Chris@145: task = KeyTask; Chris@145: } else if (eventUri == af + "KeySegment") { Chris@145: task = KeyTask; Chris@145: } else if (eventUri == af + "Onset") { Chris@145: task = OnsetTask; Chris@145: } else if (eventUri == af + "NonTonalOnset") { Chris@145: task = OnsetTask; Chris@145: } else if (eventUri == af + "Segment") { Chris@145: task = SegmentTask; Chris@145: } else if (eventUri == af + "SpeechSegment") { Chris@145: task = SegmentTask; Chris@145: } else if (eventUri == af + "StructuralSegment") { Chris@145: task = SegmentTask; Chris@145: } else { Chris@145: cerr << "WARNING: Unsupported event type URI <" Chris@145: << eventUri << ">, proceeding with UnknownTask type" Chris@145: << endl; Chris@145: } Chris@145: Chris@145: } else { Chris@145: Chris@145: 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: } Chris@145: } Chris@145: Chris@145: m_tasks[transformId] = task; Chris@145: } Chris@145: Chris@145: QString Chris@145: JAMSFeatureWriter::getTaskKey(Task task) Chris@145: { Chris@145: switch (task) { Chris@145: case UnknownTask: return "unknown"; Chris@145: case BeatTask: return "beat"; Chris@145: case OnsetTask: return "onset"; Chris@145: case ChordTask: return "chord"; Chris@145: case SegmentTask: return "segment"; Chris@145: case KeyTask: return "key"; Chris@145: case NoteTask: return "note"; Chris@145: case MelodyTask: return "melody"; Chris@145: case PitchTask: return "pitch"; Chris@145: } Chris@145: return "unknown"; Chris@145: }