Chris@498: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@498: Chris@498: /* Chris@498: Sonic Annotator Chris@498: A utility for batch feature extraction from audio files. Chris@498: Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London. Chris@498: Copyright 2007-2008 QMUL. Chris@498: Chris@498: This program is free software; you can redistribute it and/or Chris@498: modify it under the terms of the GNU General Public License as Chris@498: published by the Free Software Foundation; either version 2 of the Chris@498: License, or (at your option) any later version. See the file Chris@498: COPYING included with this distribution for more information. Chris@498: */ Chris@498: Chris@498: #include Chris@498: Chris@599: #include "base/Exceptions.h" Chris@599: Chris@498: #include "RDFFeatureWriter.h" Chris@498: #include "RDFTransformFactory.h" Chris@597: #include "PluginRDFIndexer.h" Chris@498: Chris@498: #include Chris@1035: #include Chris@498: #include Chris@508: #include Chris@498: #include Chris@498: Chris@498: using namespace std; Chris@498: using Vamp::Plugin; Chris@498: using Vamp::PluginBase; Chris@498: Chris@498: RDFFeatureWriter::RDFFeatureWriter() : Chris@498: FileFeatureWriter(SupportOneFilePerTrackTransform | Chris@498: SupportOneFilePerTrack | Chris@997: SupportOneFileTotal | Chris@997: SupportStdOut, Chris@498: "n3"), Chris@498: m_plain(false), Chris@597: m_network(false), Chris@597: m_networkRetrieved(false), Chris@498: m_count(0) Chris@498: { Chris@498: } Chris@498: Chris@498: RDFFeatureWriter::~RDFFeatureWriter() Chris@498: { Chris@498: } Chris@498: Chris@998: string Chris@998: RDFFeatureWriter::getDescription() const Chris@998: { Chris@998: return "Write output in Audio Features Ontology RDF/Turtle format."; Chris@998: } Chris@998: Chris@498: RDFFeatureWriter::ParameterList Chris@498: RDFFeatureWriter::getSupportedParameters() const Chris@498: { Chris@498: ParameterList pl = FileFeatureWriter::getSupportedParameters(); Chris@498: Parameter p; Chris@498: Chris@498: p.name = "plain"; Chris@498: p.description = "Use \"plain\" RDF even if transform metadata is available."; Chris@498: p.hasArg = false; Chris@498: pl.push_back(p); Chris@498: Chris@586: p.name = "audiofile-uri"; Chris@586: p.description = "Link the output RDF to the given audio file URI instead of its actual location."; Chris@498: p.hasArg = true; Chris@498: pl.push_back(p); Chris@594: Chris@594: p.name = "track-uri"; Chris@594: p.description = "Link the output RDF to the given track URI."; Chris@594: p.hasArg = true; Chris@594: pl.push_back(p); Chris@594: Chris@594: p.name = "maker-uri"; Chris@594: p.description = "Link the track in the output RDF to the given foaf:maker URI."; Chris@594: p.hasArg = true; Chris@594: pl.push_back(p); Chris@597: Chris@597: p.name = "network"; Chris@597: p.description = "Attempt to retrieve RDF descriptions of plugins from network, if not available locally"; Chris@597: p.hasArg = false; Chris@597: pl.push_back(p); Chris@498: Chris@498: return pl; Chris@498: } Chris@498: Chris@498: void Chris@498: RDFFeatureWriter::setParameters(map ¶ms) Chris@498: { Chris@498: FileFeatureWriter::setParameters(params); Chris@498: Chris@498: for (map::iterator i = params.begin(); Chris@498: i != params.end(); ++i) { Chris@498: if (i->first == "plain") { Chris@498: m_plain = true; Chris@498: } Chris@586: if (i->first == "audiofile-uri") { Chris@594: m_userAudioFileUri = i->second.c_str(); Chris@594: } Chris@594: if (i->first == "track-uri") { Chris@594: m_userTrackUri = i->second.c_str(); Chris@594: } Chris@594: if (i->first == "maker-uri") { Chris@594: m_userMakerUri = i->second.c_str(); Chris@498: } Chris@597: if (i->first == "network") { Chris@597: m_network = true; Chris@597: } Chris@498: } Chris@498: } Chris@498: Chris@504: void Chris@504: RDFFeatureWriter::setTrackMetadata(QString trackId, Chris@504: TrackMetadata metadata) Chris@504: { Chris@686: // cerr << "setTrackMetadata: title = " << metadata.title << ", maker = " << metadata.maker << endl; Chris@504: m_metadata[trackId] = metadata; Chris@504: } Chris@504: Chris@504: void Chris@510: RDFFeatureWriter::setFixedEventTypeURI(QString uri) Chris@510: { Chris@510: m_fixedEventTypeURI = uri; Chris@510: } Chris@510: Chris@510: void Chris@504: RDFFeatureWriter::write(QString trackId, Chris@504: const Transform &transform, Chris@504: const Plugin::OutputDescriptor& output, Chris@504: const Plugin::FeatureList& features, Chris@504: std::string summaryType) Chris@498: { Chris@498: QString pluginId = transform.getPluginIdentifier(); Chris@498: Chris@498: if (m_rdfDescriptions.find(pluginId) == m_rdfDescriptions.end()) { Chris@498: Chris@597: if (m_network && !m_networkRetrieved) { Chris@597: PluginRDFIndexer::getInstance()->indexConfiguredURLs(); Chris@597: m_networkRetrieved = true; Chris@597: } Chris@597: Chris@498: m_rdfDescriptions[pluginId] = PluginRDFDescription(pluginId); Chris@498: Chris@498: if (m_rdfDescriptions[pluginId].haveDescription()) { Chris@1428: SVCERR << "NOTE: Have RDF description for plugin ID \"" Chris@686: << pluginId << "\"" << endl; Chris@498: } else { Chris@1428: SVCERR << "NOTE: No RDF description for plugin ID \"" Chris@686: << pluginId << "\"" << endl; Chris@597: if (!m_network) { Chris@1428: SVCERR << " Consider using the --rdf-network option to retrieve plugin descriptions" << endl; Chris@1428: SVCERR << " from the network where possible." << endl; Chris@597: } Chris@498: } Chris@498: } Chris@498: Chris@498: // Need to select appropriate output file for our track/transform Chris@498: // combination Chris@498: Chris@1035: QTextStream *stream = getOutputStream(trackId, transform.getIdentifier(), Chris@1035: QTextCodec::codecForName("UTF-8")); Chris@512: if (!stream) { Chris@605: throw FailedToOpenOutputStream(trackId, transform.getIdentifier()); Chris@512: } Chris@498: Chris@498: if (m_startedStreamTransforms.find(stream) == Chris@498: m_startedStreamTransforms.end()) { Chris@594: // cerr << "This stream is new, writing prefixes" << endl; Chris@498: writePrefixes(stream); Chris@498: if (m_singleFileName == "" && !m_stdout) { Chris@498: writeSignalDescription(stream, trackId); Chris@498: } Chris@498: } Chris@498: Chris@498: if (m_startedStreamTransforms[stream].find(transform) == Chris@498: m_startedStreamTransforms[stream].end()) { Chris@498: m_startedStreamTransforms[stream].insert(transform); Chris@498: writeLocalFeatureTypes Chris@730: (stream, transform, output, m_rdfDescriptions[pluginId], Chris@730: summaryType); Chris@498: } Chris@498: Chris@498: if (m_singleFileName != "" || m_stdout) { Chris@498: if (m_startedTrackIds.find(trackId) == m_startedTrackIds.end()) { Chris@498: writeSignalDescription(stream, trackId); Chris@498: m_startedTrackIds.insert(trackId); Chris@498: } Chris@498: } Chris@498: Chris@498: QString timelineURI = m_trackTimelineURIs[trackId]; Chris@498: Chris@498: if (timelineURI == "") { Chris@1428: SVCERR << "RDFFeatureWriter: INTERNAL ERROR: writing features without having established a timeline URI!" << endl; Chris@498: exit(1); Chris@498: } Chris@498: Chris@498: if (summaryType != "") { Chris@498: Chris@498: writeSparseRDF(stream, transform, output, features, Chris@498: m_rdfDescriptions[pluginId], timelineURI); Chris@498: Chris@498: } else if (m_rdfDescriptions[pluginId].haveDescription() && Chris@498: m_rdfDescriptions[pluginId].getOutputDisposition Chris@498: (output.identifier.c_str()) == Chris@498: PluginRDFDescription::OutputDense) { Chris@498: Chris@498: QString signalURI = m_trackSignalURIs[trackId]; Chris@498: Chris@498: if (signalURI == "") { Chris@1428: SVCERR << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having established a signal URI!" << endl; Chris@498: exit(1); Chris@498: } Chris@498: Chris@498: writeDenseRDF(stream, transform, output, features, Chris@498: m_rdfDescriptions[pluginId], signalURI, timelineURI); Chris@498: Chris@507: } else if (!m_plain && Chris@507: m_rdfDescriptions[pluginId].haveDescription() && Chris@507: m_rdfDescriptions[pluginId].getOutputDisposition Chris@507: (output.identifier.c_str()) == Chris@507: PluginRDFDescription::OutputTrackLevel && Chris@507: m_rdfDescriptions[pluginId].getOutputFeatureAttributeURI Chris@507: (output.identifier.c_str()) != "") { Chris@507: Chris@507: QString signalURI = m_trackSignalURIs[trackId]; Chris@507: Chris@507: if (signalURI == "") { Chris@1428: SVCERR << "RDFFeatureWriter: INTERNAL ERROR: writing track-level features without having established a signal URI!" << endl; Chris@507: exit(1); Chris@507: } Chris@507: Chris@507: writeTrackLevelRDF(stream, transform, output, features, Chris@507: m_rdfDescriptions[pluginId], signalURI); Chris@507: Chris@498: } else { Chris@498: Chris@498: writeSparseRDF(stream, transform, output, features, Chris@498: m_rdfDescriptions[pluginId], timelineURI); Chris@498: } Chris@498: } Chris@498: Chris@498: void Chris@498: RDFFeatureWriter::writePrefixes(QTextStream *sptr) Chris@498: { Chris@498: QTextStream &stream = *sptr; Chris@498: Chris@498: stream << "@prefix dc: .\n" Chris@498: << "@prefix mo: .\n" Chris@498: << "@prefix af: .\n" Chris@504: << "@prefix foaf: . \n" Chris@498: << "@prefix event: .\n" Chris@498: << "@prefix rdf: .\n" Chris@498: << "@prefix rdfs: .\n" Chris@498: << "@prefix xsd: .\n" Chris@498: << "@prefix tl: .\n" Chris@498: << "@prefix vamp: .\n" Chris@498: << "@prefix : <#> .\n\n"; Chris@498: } Chris@498: Chris@498: void Chris@590: RDFFeatureWriter::reviewFileForAppending(QString filename) Chris@590: { Chris@590: // Appending to an RDF file is tricky, because we need to ensure Chris@590: // that our URIs differ from any already in the file. This is a Chris@590: // dirty grubby low-rent way of doing that. This function is Chris@590: // called by FileFeatureWriter::getOutputFile when in append mode. Chris@590: Chris@843: // cerr << "reviewFileForAppending(" << filename << ")" << endl; Chris@590: Chris@590: QFile file(filename); Chris@590: Chris@590: // just return, don't report failure -- function that called us will do that Chris@590: if (!file.open(QIODevice::ReadOnly)) return; Chris@590: Chris@590: QTextStream in(&file); Chris@590: Chris@590: QRegExp localObjectUriWithDigits(":[^ ]+_([0-9]+) a "); Chris@590: Chris@590: while (!in.atEnd()) { Chris@590: QString line = in.readLine(); Chris@590: if (line.length() > 120) { // probably data Chris@590: continue; Chris@590: } Chris@590: if (localObjectUriWithDigits.indexIn(line) > -1) { Chris@590: QString numeric = localObjectUriWithDigits.cap(1); Chris@590: int number = numeric.toInt(); Chris@590: if (number >= m_count) m_count = number + 1; Chris@590: } Chris@590: } Chris@590: Chris@590: file.close(); Chris@590: } Chris@590: Chris@590: void Chris@498: RDFFeatureWriter::writeSignalDescription(QTextStream *sptr, Chris@498: QString trackId) Chris@498: { Chris@690: // SVDEBUG << "RDFFeatureWriter::writeSignalDescription" << endl; Chris@530: Chris@498: QTextStream &stream = *sptr; Chris@498: Chris@498: /* Chris@498: * Describe signal we're analysing (AudioFile, Signal, TimeLine, etc.) Chris@498: */ Chris@498: Christophe@615: QUrl url(trackId, QUrl::StrictMode); Chris@498: QString scheme = url.scheme().toLower(); Chris@498: bool local = (scheme == "" || scheme == "file" || scheme.length() == 1); Chris@498: Chris@498: if (local) { Chris@498: if (scheme == "") { Chris@498: url.setScheme("file"); Chris@508: url.setPath(QFileInfo(url.path()).absoluteFilePath()); Chris@498: } else if (scheme.length() == 1) { // DOS drive letter! Chris@498: url.setScheme("file"); Chris@498: url.setPath(scheme + ":" + url.path()); Chris@498: } Chris@498: } Chris@498: Chris@590: // Note reviewFileForAppending above (when opening in append mode) Chris@498: Chris@602: unsigned long signalCount = m_count++; Chris@498: Chris@498: if (m_trackSignalURIs.find(trackId) == m_trackSignalURIs.end()) { Chris@498: m_trackSignalURIs[trackId] = QString(":signal_%1").arg(signalCount); Chris@498: } Chris@498: QString signalURI = m_trackSignalURIs[trackId]; Chris@498: Chris@585: if (m_trackTrackURIs.find(trackId) == m_trackTrackURIs.end()) { Chris@585: m_trackTrackURIs[trackId] = QString(":track_%1").arg(signalCount); Chris@585: } Chris@585: QString trackURI = m_trackTrackURIs[trackId]; Chris@594: Chris@594: bool userSpecifiedTrack = false; Chris@594: if (m_userTrackUri != "") { Chris@594: trackURI = "<" + m_userTrackUri + ">"; Chris@594: m_trackTrackURIs[trackId] = trackURI; Chris@594: userSpecifiedTrack = true; Chris@594: } Chris@585: Chris@498: if (m_trackTimelineURIs.find(trackId) == m_trackTimelineURIs.end()) { Chris@498: m_trackTimelineURIs[trackId] = QString(":signal_timeline_%1").arg(signalCount); Chris@498: } Chris@498: QString timelineURI = m_trackTimelineURIs[trackId]; Chris@498: Chris@586: QString afURI = url.toEncoded().data(); Chris@594: if (m_userAudioFileUri != "") afURI = m_userAudioFileUri; Chris@586: Chris@594: bool wantTrack = (userSpecifiedTrack || Chris@594: (m_userMakerUri != "") || Chris@1144: haveTitleArtistMetadata(trackId)); Chris@594: Chris@594: // cerr << "wantTrack = " << wantTrack << " (userSpecifiedTrack = " Chris@686: // << userSpecifiedTrack << ", m_userMakerUri = " << m_userMakerUri << ", have metadata = " << (m_metadata.find(trackId) != m_metadata.end()) << ")" << endl; Chris@594: Chris@594: if (wantTrack) { Chris@594: // We only write a Track at all if we have some title/artist Chris@594: // metadata to put in it, or if the user has requested a Chris@594: // specific track URI. Otherwise we can't be sure that what Chris@594: // we have is a Track, in the publication sense -- it may just Chris@594: // be a fragment, a test file, whatever. Since we'd have no Chris@594: // metadata to associate with our Track, the only effect of Chris@594: // including a Track would be to assert that this was one, Chris@594: // which is the one thing we wouldn't know... Chris@594: TrackMetadata tm; Chris@1144: if (haveTitleArtistMetadata(trackId)) { Chris@594: tm = m_metadata[trackId]; Chris@585: } Chris@594: stream << trackURI << " a mo:Track "; Chris@594: if (tm.title != "") { Chris@594: stream << ";\n dc:title \"\"\"" << tm.title << "\"\"\" "; Chris@594: } Chris@594: if (m_userMakerUri != "") { Chris@594: stream << ";\n foaf:maker <" << m_userMakerUri << "> "; Chris@594: } else if (tm.maker != "") { Chris@594: stream << ";\n foaf:maker [ a mo:MusicArtist; foaf:name \"\"\"" << tm.maker << "\"\"\" ] "; Chris@594: } Chris@594: if (afURI != "") { Chris@594: stream << ";\n mo:available_as <" << afURI << "> "; Chris@594: } Chris@594: stream << ".\n\n"; Chris@585: } Chris@585: Chris@594: if (afURI != "") { Chris@586: stream << "<" << afURI << "> a mo:AudioFile ;\n"; Chris@585: stream << " mo:encodes " << signalURI << ".\n\n"; Chris@500: } Chris@500: Chris@500: stream << signalURI << " a mo:Signal ;\n"; Chris@500: Chris@500: stream << " mo:time [\n" Chris@498: << " a tl:Interval ;\n" Chris@498: << " tl:onTimeLine " Chris@498: << timelineURI << "\n ] .\n\n"; Chris@585: Chris@736: stream << timelineURI << " a tl:Timeline .\n\n"; Chris@498: } Chris@498: Chris@498: void Chris@498: RDFFeatureWriter::writeLocalFeatureTypes(QTextStream *sptr, Chris@498: const Transform &transform, Chris@498: const Plugin::OutputDescriptor &od, Chris@730: PluginRDFDescription &desc, Chris@730: std::string summaryType) Chris@498: { Chris@498: QString outputId = od.identifier.c_str(); Chris@498: QTextStream &stream = *sptr; Chris@498: Chris@507: // There is no "needFeatureType" for track-level outputs, because Chris@507: // we can't meaningfully write a feature at all if we don't know Chris@507: // what property to use for it. If the output is track level but Chris@507: // there is no feature type given, we have to revert to events. Chris@507: Chris@498: bool needEventType = false; Chris@498: bool needSignalType = false; Chris@498: Chris@498: //!!! bin names, extents and so on can be written out using e.g. vamp:bin_names ( "a" "b" "c" ) Chris@498: Chris@730: if (summaryType == "" && Chris@730: desc.getOutputDisposition(outputId) == Chris@498: PluginRDFDescription::OutputDense) { Chris@498: Chris@498: // no feature events, so may need signal type but won't need Chris@498: // event type Chris@498: Chris@498: if (m_plain) { Chris@498: Chris@498: needSignalType = true; Chris@498: Chris@498: } else if (desc.getOutputSignalTypeURI(outputId) == "") { Chris@498: Chris@498: needSignalType = true; Chris@498: } Chris@498: Chris@507: } else if (desc.getOutputDisposition(outputId) == Chris@507: PluginRDFDescription::OutputTrackLevel) { Chris@507: Chris@507: // see note above -- need to generate an event type if no Chris@507: // feature type given, or if in plain mode Chris@507: Chris@507: if (m_plain) { Chris@507: Chris@507: needEventType = true; Chris@507: Chris@507: } else if (desc.getOutputFeatureAttributeURI(outputId) == "") { Chris@507: Chris@507: if (desc.getOutputEventTypeURI(outputId) == "") { Chris@507: Chris@507: needEventType = true; Chris@507: } Chris@507: } Chris@507: Chris@498: } else { Chris@498: Chris@498: // may need event type but won't need signal type Chris@498: Chris@498: if (m_plain) { Chris@498: Chris@498: needEventType = true; Chris@498: Chris@498: } else if (desc.getOutputEventTypeURI(outputId) == "") { Chris@498: Chris@498: needEventType = true; Chris@498: } Chris@498: } Chris@498: Chris@498: QString transformUri; Chris@498: if (m_transformURIs.find(transform) != m_transformURIs.end()) { Chris@498: transformUri = m_transformURIs[transform]; Chris@498: } else { Chris@498: transformUri = QString(":transform_%1_%2").arg(m_count++).arg(outputId); Chris@498: m_transformURIs[transform] = transformUri; Chris@498: } Chris@498: Chris@500: if (transform.getIdentifier() != "") { Chris@508: stream << endl Chris@508: << RDFTransformFactory::writeTransformToRDF(transform, transformUri) Chris@500: << endl; Chris@500: } Chris@498: Chris@510: if (needEventType && m_fixedEventTypeURI == "") { Chris@498: Chris@498: QString uri; Chris@734: if (m_syntheticEventTypeURIs.find(transform) != Chris@498: m_syntheticEventTypeURIs.end()) { Chris@498: uri = m_syntheticEventTypeURIs[transform]; Chris@498: } else { Chris@498: uri = QString(":event_type_%1").arg(m_count++); Chris@498: m_syntheticEventTypeURIs[transform] = uri; Chris@498: } Chris@498: Chris@498: stream << uri Chris@498: << " rdfs:subClassOf event:Event ;" << endl Chris@498: << " dc:title \"" << od.name.c_str() << "\" ;" << endl Chris@498: << " dc:format \"" << od.unit.c_str() << "\" ;" << endl Chris@498: << " dc:description \"" << od.description.c_str() << "\" ." Chris@498: << endl << endl; Chris@498: } Chris@498: Chris@498: if (needSignalType) { Chris@498: Chris@498: QString uri; Chris@498: if (m_syntheticSignalTypeURIs.find(transform) != Chris@498: m_syntheticSignalTypeURIs.end()) { Chris@498: uri = m_syntheticSignalTypeURIs[transform]; Chris@498: } else { Chris@498: uri = QString(":signal_type_%1").arg(m_count++); Chris@498: m_syntheticSignalTypeURIs[transform] = uri; Chris@498: } Chris@498: Chris@498: stream << uri Chris@498: << " rdfs:subClassOf af:Signal ;" << endl Chris@498: << " dc:title \"" << od.name.c_str() << "\" ;" << endl Chris@498: << " dc:format \"" << od.unit.c_str() << "\" ;" << endl Chris@498: << " dc:description \"" << od.description.c_str() << "\" ." Chris@498: << endl << endl; Chris@498: } Chris@498: } Chris@498: Chris@498: void Chris@498: RDFFeatureWriter::writeSparseRDF(QTextStream *sptr, Chris@498: const Transform &transform, Chris@498: const Plugin::OutputDescriptor& od, Chris@498: const Plugin::FeatureList& featureList, Chris@498: PluginRDFDescription &desc, Chris@498: QString timelineURI) Chris@498: { Chris@690: // SVDEBUG << "RDFFeatureWriter::writeSparseRDF: have " << featureList.size() << " features" << endl; Chris@512: Chris@498: if (featureList.empty()) return; Chris@498: QTextStream &stream = *sptr; Chris@498: Chris@498: bool plain = (m_plain || !desc.haveDescription()); Chris@498: Chris@498: QString outputId = od.identifier.c_str(); Chris@498: Chris@498: // iterate through FeatureLists Chris@498: Chris@930: for (int i = 0; i < (int)featureList.size(); ++i) { Chris@498: Chris@498: const Plugin::Feature &feature = featureList[i]; Chris@602: unsigned long featureNumber = m_count++; Chris@498: Chris@498: stream << ":event_" << featureNumber << " a "; Chris@498: Chris@510: if (m_fixedEventTypeURI != "") { Chris@510: stream << m_fixedEventTypeURI << " ;\n"; Chris@510: } else { Chris@510: QString eventTypeURI = desc.getOutputEventTypeURI(outputId); Chris@510: if (plain || eventTypeURI == "") { Chris@510: if (m_syntheticEventTypeURIs.find(transform) != Chris@510: m_syntheticEventTypeURIs.end()) { Chris@510: stream << m_syntheticEventTypeURIs[transform] << " ;\n"; Chris@510: } else { Chris@510: stream << ":event_type_" << outputId << " ;\n"; Chris@510: } Chris@498: } else { Chris@510: stream << "<" << eventTypeURI << "> ;\n"; Chris@498: } Chris@498: } Chris@498: Chris@498: QString timestamp = feature.timestamp.toString().c_str(); Chris@498: timestamp.replace(QRegExp("^ +"), ""); Chris@498: Chris@498: if (feature.hasDuration && feature.duration > Vamp::RealTime::zeroTime) { Chris@498: Chris@498: QString duration = feature.duration.toString().c_str(); Chris@498: duration.replace(QRegExp("^ +"), ""); Chris@498: Chris@498: stream << " event:time [ \n" Chris@498: << " a tl:Interval ;\n" Chris@498: << " tl:onTimeLine " << timelineURI << " ;\n" Chris@498: << " tl:beginsAt \"PT" << timestamp Chris@498: << "S\"^^xsd:duration ;\n" Chris@498: << " tl:duration \"PT" << duration Chris@498: << "S\"^^xsd:duration ;\n" Chris@498: << " ] "; Chris@498: Chris@498: } else { Chris@498: Chris@498: stream << " event:time [ \n" Chris@498: << " a tl:Instant ;\n" //location of the event in time Chris@498: << " tl:onTimeLine " << timelineURI << " ;\n" Chris@498: << " tl:at \"PT" << timestamp Chris@498: << "S\"^^xsd:duration ;\n ] "; Chris@498: } Chris@498: Chris@500: if (transform.getIdentifier() != "") { Chris@500: stream << ";\n"; Chris@500: stream << " vamp:computed_by " << m_transformURIs[transform] << " "; Chris@500: } Chris@498: Chris@498: if (feature.label.length() > 0) { Chris@498: stream << ";\n"; Chris@510: stream << " rdfs:label \"\"\"" << feature.label.c_str() << "\"\"\" "; Chris@498: } Chris@498: Chris@498: if (!feature.values.empty()) { Chris@498: stream << ";\n"; Chris@498: //!!! named bins? Chris@498: stream << " af:feature \"" << feature.values[0]; Chris@930: for (int j = 1; j < (int)feature.values.size(); ++j) { Chris@498: stream << " " << feature.values[j]; Chris@498: } Chris@498: stream << "\" "; Chris@498: } Chris@498: Chris@498: stream << ".\n"; Chris@498: } Chris@498: } Chris@498: Chris@498: void Chris@507: RDFFeatureWriter::writeTrackLevelRDF(QTextStream *sptr, Chris@930: const Transform &, Chris@507: const Plugin::OutputDescriptor& od, Chris@507: const Plugin::FeatureList& featureList, Chris@507: PluginRDFDescription &desc, Chris@507: QString signalURI) Chris@507: { Chris@507: if (featureList.empty()) return; Chris@507: QTextStream &stream = *sptr; Chris@507: Chris@930: // bool plain = (m_plain || !desc.haveDescription()); Chris@507: Chris@507: QString outputId = od.identifier.c_str(); Chris@507: QString featureUri = desc.getOutputFeatureAttributeURI(outputId); Chris@507: Chris@507: if (featureUri == "") { Chris@690: SVDEBUG << "RDFFeatureWriter::writeTrackLevelRDF: ERROR: No feature URI available -- this function should not have been called!" << endl; Chris@507: return; Chris@507: } Chris@507: Chris@930: for (int i = 0; i < (int)featureList.size(); ++i) { Chris@507: Chris@507: const Plugin::Feature &feature = featureList[i]; Chris@507: Chris@507: if (feature.values.empty()) { Chris@507: Chris@507: if (feature.label == "") continue; Chris@507: Chris@508: stream << signalURI << " " << featureUri << " \"\"\"" Chris@508: << feature.label.c_str() << "\"\"\" .\n"; Chris@507: Chris@507: } else { Chris@507: Chris@507: stream << signalURI << " " << featureUri << " \"" Chris@507: << feature.values[0] << "\"^^xsd:float .\n"; Chris@507: } Chris@507: } Chris@507: } Chris@507: Chris@507: void Chris@498: RDFFeatureWriter::writeDenseRDF(QTextStream *sptr, Chris@498: const Transform &transform, Chris@498: const Plugin::OutputDescriptor& od, Chris@498: const Plugin::FeatureList& featureList, Chris@498: PluginRDFDescription &desc, Chris@498: QString signalURI, Chris@498: QString timelineURI) Chris@498: { Chris@498: if (featureList.empty()) return; Chris@498: Chris@498: StringTransformPair sp(signalURI, transform); Chris@498: Chris@498: if (m_openDenseFeatures.find(sp) == m_openDenseFeatures.end()) { Chris@498: Chris@498: StreamBuffer b(sptr, ""); Chris@498: m_openDenseFeatures[sp] = b; Chris@498: Chris@498: QString &str(m_openDenseFeatures[sp].second); Chris@498: QTextStream stream(&str); Chris@498: Chris@498: bool plain = (m_plain || !desc.haveDescription()); Chris@498: QString outputId = od.identifier.c_str(); Chris@498: Chris@602: unsigned long featureNumber = m_count++; Chris@498: Chris@498: // need to write out feature timeline map -- for this we need Chris@498: // the sample rate, window length and hop size from the Chris@498: // transform Chris@498: Chris@498: stream << "\n:feature_timeline_" << featureNumber << " a tl:DiscreteTimeLine .\n\n"; Chris@498: Chris@1047: sv_samplerate_t sampleRate; Chris@995: int stepSize, blockSize; Chris@498: Chris@995: // If the output is FixedSampleRate, we need to draw the Chris@995: // sample rate and step size from the output descriptor; Chris@995: // otherwise they come from the transform Chris@498: Chris@995: if (od.sampleType == Plugin::OutputDescriptor::FixedSampleRate) { Chris@995: Chris@995: sampleRate = od.sampleRate; Chris@995: stepSize = 1; Chris@995: blockSize = 1; Chris@995: Chris@995: } else { Chris@995: Chris@995: sampleRate = transform.getSampleRate(); Chris@995: if (sampleRate == 0.f) { Chris@1428: SVCERR << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the sample rate properly!" << endl; Chris@995: return; Chris@995: } Chris@995: Chris@995: stepSize = transform.getStepSize(); Chris@995: if (stepSize == 0) { Chris@1428: SVCERR << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the step size properly!" << endl; Chris@995: return; Chris@995: } Chris@995: Chris@995: blockSize = transform.getBlockSize(); Chris@995: if (blockSize == 0) { Chris@1428: SVCERR << "RDFFeatureWriter: INTERNAL ERROR: writing dense features without having set the block size properly!" << endl; Chris@995: return; Chris@995: } Chris@498: } Chris@498: Chris@498: stream << ":feature_timeline_map_" << featureNumber Chris@498: << " a tl:UniformSamplingWindowingMap ;\n" Chris@498: << " tl:rangeTimeLine :feature_timeline_" << featureNumber << " ;\n" Chris@498: << " tl:domainTimeLine " << timelineURI << " ;\n" Chris@995: << " tl:sampleRate \"" << sampleRate << "\"^^xsd:float ;\n" Chris@498: << " tl:windowLength \"" << blockSize << "\"^^xsd:int ;\n" Chris@498: << " tl:hopSize \"" << stepSize << "\"^^xsd:int .\n\n"; Chris@498: Chris@498: stream << signalURI << " af:signal_feature :feature_" Chris@498: << featureNumber << " ." << endl << endl; Chris@498: Chris@498: stream << ":feature_" << featureNumber << " a "; Chris@498: Chris@498: QString signalTypeURI = desc.getOutputSignalTypeURI(outputId); Chris@498: if (plain || signalTypeURI == "") { Chris@498: if (m_syntheticSignalTypeURIs.find(transform) != Chris@498: m_syntheticSignalTypeURIs.end()) { Chris@498: stream << m_syntheticSignalTypeURIs[transform] << " ;\n"; Chris@498: } else { Chris@498: stream << ":signal_type_" << outputId << " ;\n"; Chris@498: } Chris@498: } else { Chris@587: stream << "<" << signalTypeURI << "> ;\n"; Chris@498: } Chris@498: Chris@498: stream << " mo:time [" Chris@498: << "\n a tl:Interval ;" Chris@498: << "\n tl:onTimeLine :feature_timeline_" << featureNumber << " ;"; Chris@498: Chris@498: RealTime startrt = transform.getStartTime(); Chris@498: RealTime durationrt = transform.getDuration(); Chris@498: Chris@1039: sv_frame_t start = RealTime::realTime2Frame Chris@1047: (startrt, sampleRate) / stepSize; Chris@1039: sv_frame_t duration = RealTime::realTime2Frame Chris@1047: (durationrt, sampleRate) / stepSize; Chris@498: Chris@498: if (start != 0) { Chris@498: stream << "\n tl:start \"" << start << "\"^^xsd:int ;"; Chris@498: } Chris@498: if (duration != 0) { Chris@498: stream << "\n tl:duration \"" << duration << "\"^^xsd:int ;"; Chris@498: } Chris@498: Chris@498: stream << "\n ] ;\n"; Chris@498: Chris@584: if (transform.getIdentifier() != "") { Chris@584: stream << " vamp:computed_by " << m_transformURIs[transform] << " ;\n"; Chris@584: } Chris@584: Chris@498: if (od.hasFixedBinCount) { Chris@498: // We only know the height, so write the width as zero Chris@498: stream << " af:dimensions \"" << od.binCount << " 0\" ;\n"; Chris@498: } Chris@498: Chris@498: stream << " af:value \""; Chris@498: } Chris@498: Chris@498: QString &str = m_openDenseFeatures[sp].second; Chris@498: QTextStream stream(&str); Chris@498: Chris@930: for (int i = 0; i < (int)featureList.size(); ++i) { Chris@498: Chris@498: const Plugin::Feature &feature = featureList[i]; Chris@498: Chris@930: for (int j = 0; j < (int)feature.values.size(); ++j) { Chris@498: stream << feature.values[j] << " "; Chris@498: } Chris@498: } Chris@498: } Chris@498: Chris@498: void RDFFeatureWriter::finish() Chris@498: { Chris@690: // SVDEBUG << "RDFFeatureWriter::finish()" << endl; Chris@498: Chris@498: // close any open dense feature literals Chris@498: Chris@498: for (map::iterator i = Chris@498: m_openDenseFeatures.begin(); Chris@498: i != m_openDenseFeatures.end(); ++i) { Chris@690: // SVDEBUG << "closing a stream" << endl; Chris@498: StreamBuffer &b = i->second; Chris@498: *(b.first) << b.second << "\" ." << endl; Chris@498: } Chris@498: Chris@498: m_openDenseFeatures.clear(); Chris@530: m_startedStreamTransforms.clear(); Chris@530: Chris@530: FileFeatureWriter::finish(); Chris@498: } Chris@498: Chris@498: