Chris@439: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@439: Chris@439: /* Chris@439: Sonic Visualiser Chris@439: An audio file viewer and annotation editor. Chris@439: Centre for Digital Music, Queen Mary, University of London. Chris@727: This file copyright 2008-2012 QMUL. Chris@439: Chris@439: This program is free software; you can redistribute it and/or Chris@439: modify it under the terms of the GNU General Public License as Chris@439: published by the Free Software Foundation; either version 2 of the Chris@439: License, or (at your option) any later version. See the file Chris@439: COPYING included with this distribution for more information. Chris@439: */ Chris@439: Chris@439: #include "RDFTransformFactory.h" Chris@439: Chris@439: #include Chris@439: #include Chris@439: Chris@494: #include Chris@592: #include Chris@494: Chris@439: #include Chris@439: #include Chris@439: Chris@439: #include "PluginRDFIndexer.h" Chris@494: #include "PluginRDFDescription.h" Chris@439: #include "base/ProgressReporter.h" Chris@503: #include "plugin/PluginIdentifier.h" Chris@439: Chris@439: #include "transform/TransformFactory.h" Chris@439: Chris@727: #include Chris@727: #include Chris@727: Chris@727: using Dataquay::Uri; Chris@727: using Dataquay::Node; Chris@727: using Dataquay::Nodes; Chris@727: using Dataquay::Triple; Chris@727: using Dataquay::Triples; Chris@727: using Dataquay::BasicStore; Chris@727: using Dataquay::PropertyObject; Chris@439: Chris@439: Chris@439: class RDFTransformFactoryImpl Chris@439: { Chris@439: public: Chris@439: RDFTransformFactoryImpl(QString url); Chris@439: virtual ~RDFTransformFactoryImpl(); Chris@439: Chris@493: bool isRDF(); Chris@439: bool isOK(); Chris@439: QString getErrorString() const; Chris@439: Chris@439: std::vector getTransforms(ProgressReporter *); Chris@439: Chris@494: static QString writeTransformToRDF(const Transform &, QString); Chris@494: Chris@439: protected: Chris@727: BasicStore *m_store; Chris@439: QString m_urlString; Chris@439: QString m_errorString; Chris@493: bool m_isRDF; Chris@489: bool setOutput(Transform &, QString); Chris@489: bool setParameters(Transform &, QString); Chris@439: }; Chris@439: Chris@439: Chris@439: QString Chris@439: RDFTransformFactory::getKnownExtensions() Chris@439: { Chris@439: return "*.rdf *.n3 *.ttl"; Chris@439: } Chris@439: Chris@439: RDFTransformFactory::RDFTransformFactory(QString url) : Chris@439: m_d(new RDFTransformFactoryImpl(url)) Chris@439: { Chris@439: } Chris@439: Chris@439: RDFTransformFactory::~RDFTransformFactory() Chris@439: { Chris@439: delete m_d; Chris@439: } Chris@439: Chris@439: bool Chris@493: RDFTransformFactory::isRDF() Chris@493: { Chris@493: return m_d->isRDF(); Chris@493: } Chris@493: Chris@493: bool Chris@439: RDFTransformFactory::isOK() Chris@439: { Chris@439: return m_d->isOK(); Chris@439: } Chris@439: Chris@439: QString Chris@439: RDFTransformFactory::getErrorString() const Chris@439: { Chris@439: return m_d->getErrorString(); Chris@439: } Chris@439: Chris@439: std::vector Chris@439: RDFTransformFactory::getTransforms(ProgressReporter *r) Chris@439: { Chris@439: return m_d->getTransforms(r); Chris@439: } Chris@439: Chris@494: QString Chris@494: RDFTransformFactory::writeTransformToRDF(const Transform &t, QString f) Chris@494: { Chris@494: return RDFTransformFactoryImpl::writeTransformToRDF(t, f); Chris@494: } Chris@494: Chris@439: RDFTransformFactoryImpl::RDFTransformFactoryImpl(QString url) : Chris@727: m_store(new BasicStore), Chris@493: m_urlString(url), Chris@493: m_isRDF(false) Chris@439: { Chris@727: //!!! retrieve data if remote... then Chris@727: m_store->addPrefix("vamp", Uri("http://purl.org/ontology/vamp/")); Chris@727: try { Chris@730: QUrl qurl; Chris@730: if (url.startsWith("file:")) { Chris@730: qurl = QUrl(url); Chris@730: } else { Chris@730: qurl = QUrl::fromLocalFile(url); Chris@730: } Chris@730: m_store->import(qurl, BasicStore::ImportIgnoreDuplicates); Chris@727: m_isRDF = true; Chris@1163: } catch (const std::exception &e) { Chris@1163: // The file is not RDF -- we report this by returning false Chris@1163: // from isRDF (because we have not reached the m_isRDF = true Chris@1163: // line above), but we also set the error string Chris@1163: m_errorString = e.what(); Chris@1163: } Chris@439: } Chris@439: Chris@439: RDFTransformFactoryImpl::~RDFTransformFactoryImpl() Chris@439: { Chris@727: delete m_store; Chris@439: } Chris@439: Chris@439: bool Chris@493: RDFTransformFactoryImpl::isRDF() Chris@493: { Chris@493: return m_isRDF; Chris@493: } Chris@493: Chris@493: bool Chris@439: RDFTransformFactoryImpl::isOK() Chris@439: { Chris@1163: return m_isRDF && (m_errorString == ""); Chris@439: } Chris@439: Chris@439: QString Chris@439: RDFTransformFactoryImpl::getErrorString() const Chris@439: { Chris@439: return m_errorString; Chris@439: } Chris@439: Chris@439: std::vector Chris@930: RDFTransformFactoryImpl::getTransforms(ProgressReporter *) Chris@439: { Chris@439: std::vector transforms; Chris@439: Chris@1163: if (!m_isRDF) return transforms; Chris@1163: Chris@440: std::map uriTransformMap; Chris@439: Chris@727: Nodes tnodes = m_store->match Chris@730: (Triple(Node(), Uri("a"), m_store->expand("vamp:Transform"))).subjects(); Chris@439: Chris@728: PluginRDFIndexer *indexer = PluginRDFIndexer::getInstance(); Chris@728: Chris@727: foreach (Node tnode, tnodes) { Chris@439: Chris@730: Node pnode = m_store->complete Chris@730: (Triple(tnode, m_store->expand("vamp:plugin"), Node())); Chris@440: Chris@728: if (pnode == Node()) { Chris@728: cerr << "RDFTransformFactory: WARNING: No vamp:plugin for " Chris@728: << "vamp:Transform node " << tnode Chris@728: << ", skipping this transform" << endl; Chris@728: continue; Chris@728: } Chris@440: Chris@728: QString transformUri = tnode.value; Chris@728: QString pluginUri = pnode.value; Chris@439: Chris@439: QString pluginId = indexer->getIdForPluginURI(pluginUri); Chris@439: if (pluginId == "") { Chris@439: cerr << "RDFTransformFactory: WARNING: Unknown plugin <" Chris@686: << pluginUri << "> for transform <" Chris@686: << transformUri << ">, skipping this transform" Chris@440: << endl; Chris@440: continue; Chris@440: } Chris@440: Chris@439: Transform transform; Chris@439: transform.setPluginIdentifier(pluginId); Chris@439: Chris@489: if (!setOutput(transform, transformUri)) { Chris@439: return transforms; Chris@439: } Chris@439: Chris@489: if (!setParameters(transform, transformUri)) { Chris@439: return transforms; Chris@439: } Chris@439: Chris@440: uriTransformMap[transformUri] = transform; Chris@439: Chris@489: static const char *optionals[] = { Chris@489: "program", Chris@508: "summary_type", Chris@489: "step_size", Chris@489: "block_size", Chris@489: "window_type", Chris@489: "sample_rate", Chris@489: "start", Chris@994: "duration", Chris@994: "plugin_version" Chris@489: }; Chris@489: Chris@930: for (int j = 0; j < int(sizeof(optionals)/sizeof(optionals[0])); ++j) { Chris@439: Chris@489: QString optional = optionals[j]; Chris@489: Chris@730: Node onode = m_store->complete Chris@730: (Triple(Uri(transformUri), Chris@730: m_store->expand(QString("vamp:") + optional), Node())); Chris@440: Chris@728: if (onode.type != Node::Literal) continue; Chris@440: Chris@728: if (optional == "program") { Chris@728: transform.setProgram(onode.value); Chris@728: } else if (optional == "summary_type") { Chris@728: transform.setSummaryType Chris@728: (transform.stringToSummaryType(onode.value)); Chris@728: } else if (optional == "step_size") { Chris@728: transform.setStepSize(onode.value.toUInt()); Chris@728: } else if (optional == "block_size") { Chris@728: transform.setBlockSize(onode.value.toUInt()); Chris@728: } else if (optional == "window_type") { Chris@728: transform.setWindowType Chris@728: (Window::getTypeForName Chris@728: (onode.value.toLower().toStdString())); Chris@728: } else if (optional == "sample_rate") { Chris@728: transform.setSampleRate(onode.value.toFloat()); Chris@728: } else if (optional == "start") { Chris@987: RealTime start = RealTime::fromXsdDuration(onode.value.toStdString()); Chris@987: transform.setStartTime(start); Chris@728: } else if (optional == "duration") { Chris@987: RealTime duration = RealTime::fromXsdDuration(onode.value.toStdString()); Chris@987: transform.setDuration(duration); Chris@987: if (duration == RealTime::zeroTime) { Chris@987: cerr << "\nRDFTransformFactory: WARNING: Duration is specified as \"" << onode.value << "\" in RDF file,\n but this evaluates to zero when parsed as an xsd:duration datatype.\n The duration property will therefore be ignored.\n To specify start time and duration use the xsd:duration format,\n for example \"PT2.5S\"^^xsd:duration (for 2.5 seconds).\n\n"; Chris@987: } Chris@994: } else if (optional == "plugin_version") { Chris@994: transform.setPluginVersion(onode.value); Chris@728: } else { Chris@728: cerr << "RDFTransformFactory: ERROR: Inconsistent optionals lists (unexpected optional \"" << optional << "\"" << endl; Chris@440: } Chris@440: } Chris@440: Chris@730: cerr << "RDFTransformFactory: NOTE: Transform is: " << endl; Chris@686: cerr << transform.toXmlString() << endl; Chris@439: Chris@439: transforms.push_back(transform); Chris@439: } Chris@439: Chris@439: return transforms; Chris@439: } Chris@439: Chris@440: bool Chris@440: RDFTransformFactoryImpl::setOutput(Transform &transform, Chris@489: QString transformUri) Chris@440: { Chris@730: Node outputNode = m_store->complete Chris@730: (Triple(Uri(transformUri), m_store->expand("vamp:output"), Node())); Chris@728: Chris@728: if (outputNode == Node()) return true; Chris@489: Chris@728: if (outputNode.type != Node::URI && outputNode.type != Node::Blank) { Chris@728: m_errorString = QString("vamp:output for output of transform <%1> is not a URI or blank node").arg(transformUri); Chris@728: return false; Chris@489: } Chris@728: Chris@728: // Now, outputNode might be the subject of a triple within m_store Chris@728: // that tells us the vamp:identifier, or it might be the subject Chris@728: // of a triple within the indexer that tells us it Chris@728: Chris@730: Node identNode = m_store->complete Chris@730: (Triple(outputNode, m_store->expand("vamp:identifier"), Node())); Chris@728: Chris@728: if (identNode == Node()) { Chris@728: PluginRDFIndexer *indexer = PluginRDFIndexer::getInstance(); Chris@728: const BasicStore *index = indexer->getIndex(); Chris@730: identNode = index->complete Chris@730: (Triple(outputNode, index->expand("vamp:identifier"), Node())); Chris@728: } Chris@728: Chris@728: if (identNode == Node() || identNode.type != Node::Literal) { Chris@494: m_errorString = QString("No vamp:identifier found for output of transform <%1>, or vamp:identifier is not a literal").arg(transformUri); Chris@489: return false; Chris@489: } Chris@489: Chris@728: transform.setOutput(identNode.value); Chris@440: Chris@440: return true; Chris@440: } Chris@440: Chris@440: Chris@440: bool Chris@440: RDFTransformFactoryImpl::setParameters(Transform &transform, Chris@489: QString transformUri) Chris@440: { Chris@729: Nodes bindings = m_store->match Chris@730: (Triple(Uri(transformUri), m_store->expand("vamp:parameter_binding"), Node())).objects(); Chris@440: Chris@729: foreach (Node binding, bindings) { Chris@729: Chris@730: Node paramNode = m_store->complete Chris@730: (Triple(binding, m_store->expand("vamp:parameter"), Node())); Chris@729: Chris@729: if (paramNode == Node()) { Chris@729: cerr << "RDFTransformFactoryImpl::setParameters: No vamp:parameter for binding " << binding << endl; Chris@729: continue; Chris@729: } Chris@729: Chris@730: Node valueNode = m_store->complete Chris@730: (Triple(binding, m_store->expand("vamp:value"), Node())); Chris@729: Chris@729: if (paramNode == Node()) { Chris@729: cerr << "RDFTransformFactoryImpl::setParameters: No vamp:value for binding " << binding << endl; Chris@729: continue; Chris@729: } Chris@730: Chris@730: // As with output above, paramNode might be the subject of a Chris@730: // triple within m_store that tells us the vamp:identifier, or Chris@730: // it might be the subject of a triple within the indexer that Chris@730: // tells us it Chris@729: Chris@730: Node idNode = m_store->complete Chris@730: (Triple(paramNode, m_store->expand("vamp:identifier"), Node())); Chris@730: Chris@729: if (idNode == Node()) { Chris@730: PluginRDFIndexer *indexer = PluginRDFIndexer::getInstance(); Chris@730: const BasicStore *index = indexer->getIndex(); Chris@730: idNode = index->complete Chris@730: (Triple(paramNode, index->expand("vamp:identifier"), Node())); Chris@730: } Chris@730: Chris@730: if (idNode == Node() || idNode.type != Node::Literal) { Chris@729: cerr << "RDFTransformFactoryImpl::setParameters: No vamp:identifier for parameter " << paramNode << endl; Chris@729: continue; Chris@729: } Chris@440: Chris@729: transform.setParameter(idNode.value, valueNode.value.toFloat()); Chris@440: } Chris@440: Chris@440: return true; Chris@440: } Chris@440: Chris@494: QString Chris@494: RDFTransformFactoryImpl::writeTransformToRDF(const Transform &transform, Chris@494: QString uri) Chris@494: { Chris@494: QString str; Chris@494: QTextStream s(&str); Chris@494: Chris@503: // assumes the usual prefixes are available; requires that uri be Chris@503: // a local fragment (e.g. ":transform") rather than a uri enclosed Chris@503: // in <>, so that we can suffix it if need be Chris@494: Chris@494: QString pluginId = transform.getPluginIdentifier(); Chris@494: QString pluginUri = PluginRDFIndexer::getInstance()->getURIForPluginId(pluginId); Chris@494: Chris@503: if (pluginUri != "") { Chris@503: s << uri << " a vamp:Transform ;" << endl; Chris@592: s << " vamp:plugin <" << QUrl(pluginUri).toEncoded().data() << "> ;" << endl; Chris@503: } else { Chris@843: cerr << "WARNING: RDFTransformFactory::writeTransformToRDF: No plugin URI available for plugin id \"" << pluginId << "\", writing synthetic plugin and library resources" << endl; Chris@503: QString type, soname, label; Chris@503: PluginIdentifier::parseIdentifier(pluginId, type, soname, label); Chris@503: s << uri << "_plugin a vamp:Plugin ;" << endl; Chris@503: s << " vamp:identifier \"" << label << "\" .\n" << endl; Chris@503: s << uri << "_library a vamp:PluginLibrary ;" << endl; Chris@503: s << " vamp:identifier \"" << soname << "\" ;" << endl; Chris@503: s << " vamp:available_plugin " << uri << "_plugin .\n" << endl; Chris@503: s << uri << " a vamp:Transform ;" << endl; Chris@503: s << " vamp:plugin " << uri << "_plugin ;" << endl; Chris@503: } Chris@503: Chris@494: PluginRDFDescription description(pluginId); Chris@503: QString outputId = transform.getOutput(); Chris@503: QString outputUri = description.getOutputUri(outputId); Chris@494: Chris@494: if (transform.getOutput() != "" && outputUri == "") { Chris@843: cerr << "WARNING: RDFTransformFactory::writeTransformToRDF: No output URI available for transform output id \"" << transform.getOutput() << "\", writing a synthetic output resource" << endl; Chris@494: } Chris@494: Chris@494: if (transform.getStepSize() != 0) { Chris@494: s << " vamp:step_size \"" << transform.getStepSize() << "\"^^xsd:int ; " << endl; Chris@494: } Chris@494: if (transform.getBlockSize() != 0) { Chris@494: s << " vamp:block_size \"" << transform.getBlockSize() << "\"^^xsd:int ; " << endl; Chris@494: } Chris@1004: if (transform.getWindowType() != HanningWindow) { Chris@1004: s << " vamp:window_type \"" << Chris@1004: Window::getNameForType(transform.getWindowType()).c_str() Chris@1004: << "\" ; " << endl; Chris@1004: } Chris@494: if (transform.getStartTime() != RealTime::zeroTime) { Chris@494: s << " vamp:start \"" << transform.getStartTime().toXsdDuration().c_str() << "\"^^xsd:duration ; " << endl; Chris@494: } Chris@494: if (transform.getDuration() != RealTime::zeroTime) { Chris@494: s << " vamp:duration \"" << transform.getDuration().toXsdDuration().c_str() << "\"^^xsd:duration ; " << endl; Chris@494: } Chris@494: if (transform.getSampleRate() != 0) { Chris@494: s << " vamp:sample_rate \"" << transform.getSampleRate() << "\"^^xsd:float ; " << endl; Chris@494: } Chris@1003: if (transform.getPluginVersion() != "") { Chris@1003: s << " vamp:plugin_version \"\"\"" << transform.getPluginVersion() << "\"\"\" ; " << endl; Chris@1003: } Chris@494: Chris@494: QString program = transform.getProgram(); Chris@494: if (program != "") { Chris@494: s << " vamp:program \"\"\"" << program << "\"\"\" ;" << endl; Chris@494: } Chris@494: Chris@508: QString summary = transform.summaryTypeToString(transform.getSummaryType()); Chris@508: if (summary != "") { Chris@508: s << " vamp:summary_type \"" << summary << "\" ;" << endl; Chris@508: } Chris@508: Chris@494: Transform::ParameterMap parameters = transform.getParameters(); Chris@494: for (Transform::ParameterMap::const_iterator i = parameters.begin(); Chris@494: i != parameters.end(); ++i) { Chris@494: QString name = i->first; Chris@494: float value = i->second; Chris@494: s << " vamp:parameter_binding [" << endl; Chris@494: s << " vamp:parameter [ vamp:identifier \"" << name << "\" ] ;" << endl; Chris@494: s << " vamp:value \"" << value << "\"^^xsd:float ;" << endl; Chris@494: s << " ] ;" << endl; Chris@494: } Chris@494: Chris@494: if (outputUri != "") { Chris@592: s << " vamp:output <" << QUrl(outputUri).toEncoded().data() << "> ." << endl; Chris@503: } else if (outputId != "") { Chris@503: s << " vamp:output [ vamp:identifier \"" << outputId << "\" ] ." << endl; Chris@494: } else { Chris@494: s << " ." << endl; Chris@494: } Chris@494: Chris@494: return str; Chris@494: } Chris@494: