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@439: This file copyright 2008 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 "SimpleSPARQLQuery.h" 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@439: using std::cerr; Chris@439: using std::endl; Chris@439: Chris@439: typedef const unsigned char *STR; // redland's expected string type 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@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@493: m_urlString(url), Chris@493: m_isRDF(false) Chris@439: { Chris@439: } Chris@439: Chris@439: RDFTransformFactoryImpl::~RDFTransformFactoryImpl() Chris@439: { Chris@492: SimpleSPARQLQuery::closeSingleSource(m_urlString); 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@439: return (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@439: RDFTransformFactoryImpl::getTransforms(ProgressReporter *reporter) Chris@439: { Chris@439: std::vector transforms; Chris@439: Chris@440: std::map uriTransformMap; Chris@439: Chris@489: QString query = Chris@440: " PREFIX vamp: " Chris@439: Chris@489: " SELECT ?transform ?plugin " Chris@440: Chris@440: " FROM <%2> " Chris@439: Chris@440: " WHERE { " Chris@440: " ?transform a vamp:Transform ; " Chris@440: " vamp:plugin ?plugin . " Chris@440: " } "; Chris@440: Chris@440: SimpleSPARQLQuery transformsQuery Chris@489: (SimpleSPARQLQuery::QueryFromSingleSource, query.arg(m_urlString)); Chris@440: Chris@440: SimpleSPARQLQuery::ResultList transformResults = transformsQuery.execute(); Chris@440: Chris@440: if (!transformsQuery.isOK()) { Chris@440: m_errorString = transformsQuery.getErrorString(); Chris@439: return transforms; Chris@439: } Chris@439: Chris@493: m_isRDF = true; Chris@493: Chris@440: if (transformResults.empty()) { Chris@687: DEBUG << "RDFTransformFactory: NOTE: No RDF/TTL transform descriptions found in document at <" << m_urlString << ">" << endl; Chris@439: return transforms; Chris@439: } Chris@439: Chris@489: // There are various queries we need to make that might include Chris@494: // data from either the transform RDF or the model accumulated Chris@489: // from plugin descriptions. For example, the transform RDF may Chris@489: // specify the output's true URI, or it might have a blank node or Chris@489: // some other URI with the appropriate vamp:identifier included in Chris@489: // the file. To cover both cases, we need to add the file itself Chris@489: // into the model and always query the model using the transform Chris@489: // URI rather than querying the file itself subsequently. Chris@489: Chris@489: SimpleSPARQLQuery::addSourceToModel(m_urlString); Chris@489: Chris@439: PluginRDFIndexer *indexer = PluginRDFIndexer::getInstance(); Chris@439: Chris@440: for (int i = 0; i < transformResults.size(); ++i) { Chris@439: Chris@440: SimpleSPARQLQuery::KeyValueMap &result = transformResults[i]; Chris@439: Chris@439: QString transformUri = result["transform"].value; Chris@439: QString pluginUri = result["plugin"].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: // We have to do this a very long way round, to work around Chris@489: // rasqal's current inability to handle correctly more than one Chris@489: // OPTIONAL graph in a query Chris@439: Chris@489: static const char *optionals[] = { Chris@489: "output", 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@489: "duration" Chris@489: }; Chris@489: Chris@489: for (int j = 0; j < sizeof(optionals)/sizeof(optionals[0]); ++j) { Chris@439: Chris@489: QString optional = optionals[j]; Chris@489: Chris@489: QString queryTemplate = Chris@489: " PREFIX vamp: " Chris@489: Chris@489: " SELECT ?%1 " Chris@489: Chris@489: " WHERE { " Chris@489: " <%2> vamp:%1 ?%1 " Chris@489: " } "; Chris@489: Chris@489: SimpleSPARQLQuery query Chris@489: (SimpleSPARQLQuery::QueryFromModel, Chris@489: queryTemplate.arg(optional).arg(transformUri)); Chris@440: Chris@489: SimpleSPARQLQuery::ResultList results = query.execute(); Chris@440: Chris@489: if (!query.isOK()) { Chris@489: m_errorString = query.getErrorString(); Chris@489: return transforms; Chris@440: } Chris@440: Chris@489: if (results.empty()) continue; Chris@440: Chris@489: for (int k = 0; k < results.size(); ++k) { Chris@489: Chris@489: const SimpleSPARQLQuery::Value &v = results[k][optional]; Chris@489: Chris@489: if (v.type == SimpleSPARQLQuery::LiteralValue) { Chris@440: Chris@489: if (optional == "program") { Chris@489: transform.setProgram(v.value); Chris@508: } else if (optional == "summary_type") { Chris@508: transform.setSummaryType Chris@508: (transform.stringToSummaryType(v.value)); Chris@489: } else if (optional == "step_size") { Chris@489: transform.setStepSize(v.value.toUInt()); Chris@489: } else if (optional == "block_size") { Chris@489: transform.setBlockSize(v.value.toUInt()); Chris@489: } else if (optional == "window_type") { Chris@687: DEBUG << "NOTE: can't handle window type yet (value is \"" Chris@686: << v.value << "\")" << endl; Chris@489: } else if (optional == "sample_rate") { Chris@489: transform.setSampleRate(v.value.toFloat()); Chris@489: } else if (optional == "start") { Chris@489: transform.setStartTime Chris@489: (RealTime::fromXsdDuration(v.value.toStdString())); Chris@489: } else if (optional == "duration") { Chris@489: transform.setDuration Chris@489: (RealTime::fromXsdDuration(v.value.toStdString())); Chris@489: } else { Chris@686: cerr << "RDFTransformFactory: ERROR: Inconsistent optionals lists (unexpected optional \"" << optional << "\"" << endl; Chris@489: } Chris@440: } Chris@440: } Chris@440: } Chris@440: Chris@687: DEBUG << "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@489: SimpleSPARQLQuery::Value outputValue = Chris@489: SimpleSPARQLQuery::singleResultQuery Chris@489: (SimpleSPARQLQuery::QueryFromModel, Chris@489: QString Chris@489: ( Chris@489: " PREFIX vamp: " Chris@489: Chris@494: " SELECT ?output_id " Chris@489: Chris@489: " WHERE { " Chris@489: " <%1> vamp:output ?output . " Chris@494: " ?output vamp:identifier ?output_id " Chris@489: " } " Chris@489: ) Chris@489: .arg(transformUri), Chris@494: "output_id"); Chris@489: Chris@489: if (outputValue.type == SimpleSPARQLQuery::NoValue) { Chris@489: return true; Chris@489: } Chris@494: Chris@494: if (outputValue.type != SimpleSPARQLQuery::LiteralValue) { 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@494: transform.setOutput(outputValue.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@440: SimpleSPARQLQuery paramQuery Chris@489: (SimpleSPARQLQuery::QueryFromModel, Chris@480: QString Chris@440: ( Chris@440: " PREFIX vamp: " Chris@440: Chris@440: " SELECT ?param_id ?param_value " Chris@440: Chris@440: " WHERE { " Chris@489: " <%1> vamp:parameter_binding ?binding . " Chris@440: " ?binding vamp:parameter ?param ; " Chris@440: " vamp:value ?param_value . " Chris@440: " ?param vamp:identifier ?param_id " Chris@440: " } " Chris@440: ) Chris@440: .arg(transformUri)); Chris@440: Chris@440: SimpleSPARQLQuery::ResultList paramResults = paramQuery.execute(); Chris@440: Chris@440: if (!paramQuery.isOK()) { Chris@440: m_errorString = paramQuery.getErrorString(); Chris@440: return false; Chris@440: } Chris@440: Chris@440: if (paramQuery.wasCancelled()) { Chris@440: m_errorString = "Query cancelled"; Chris@440: return false; Chris@440: } Chris@440: Chris@440: for (int j = 0; j < paramResults.size(); ++j) { Chris@440: Chris@440: QString paramId = paramResults[j]["param_id"].value; Chris@440: QString paramValue = paramResults[j]["param_value"].value; Chris@440: Chris@440: if (paramId == "" || paramValue == "") continue; Chris@440: Chris@440: transform.setParameter(paramId, paramValue.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@686: std::cerr << "WARNING: RDFTransformFactory::writeTransformToRDF: No plugin URI available for plugin id \"" << pluginId << "\", writing synthetic plugin and library resources" << std::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@686: std::cerr << "WARNING: RDFTransformFactory::writeTransformToRDF: No output URI available for transform output id \"" << transform.getOutput() << "\", writing a synthetic output resource" << std::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@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@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: