Mercurial > hg > classical
view utilities/the-application/the-application.cpp @ 53:bcea875d8d2f tip
More build fixes
author | Chris Cannam |
---|---|
date | Thu, 16 Oct 2014 19:03:51 +0100 |
parents | c8b777862198 |
children |
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ #include "Objects.h" #include "TypeRegistrar.h" #include "FeatureFileIndex.h" #include <dataquay/BasicStore.h> #include <dataquay/TransactionalStore.h> #include <dataquay/RDFException.h> #include <dataquay/objectmapper/ObjectLoader.h> #include <dataquay/objectmapper/ObjectStorer.h> #include <dataquay/objectmapper/ObjectMapper.h> #include <dataquay/objectmapper/TypeMapping.h> #include "data/fileio/AudioFileReaderFactory.h" #include "data/fileio/AudioFileReader.h" #include "base/TempDirectory.h" #include "Matcher.h" #include <vamp-hostsdk/PluginLoader.h> #include <QMultiMap> #include <QFileInfo> #include <QDir> #include <QCoreApplication> #include <iostream> using namespace Dataquay; using namespace ClassicalData; using namespace std; using namespace Vamp; using namespace Vamp::HostExt; /* ostream &operator<<(ostream &target, const QString &str) { return target << str.toLocal8Bit().data(); } ostream &operator<<(ostream &target, const QUrl &u) { return target << "<" << u.toString() << ">"; } */ bool load(BasicStore *store, QString fileName) { QUrl url = QUrl::fromLocalFile(fileName); cerr << "Importing from URL " << url << " ..."; try { store->import(url, BasicStore::ImportPermitDuplicates); } catch (RDFException e) { cerr << " retrying with explicit ntriples type..."; try { store->import(url, BasicStore::ImportPermitDuplicates, "ntriples"); } catch (RDFException e) { cerr << "failed" << endl; cerr << "Import failed: " << e.what() << endl; return false; } } cerr << " done" << endl; return true; } void usage(char *name) { int s = 0; for (int i = 0; name[i]; ++i) if (name[i] == '/') s = i + 1; name = name + s; cerr << "Usage:" << endl; cerr << " " << name << " <input-rdf-file> guess <track.wav> [<track.wav> ...]" << endl; exit(-1); } static QList<Composer *> allComposers; static QHash<QString, Composer *> composerAliases; static QHash<Uri, Composer *> composerUris; static QMap<Composer *, QList<Work *> > worksMap; static QList<Work *> allWorks; void show(Composer *c) { cout << c->property("uri").value<Uri>() << endl; cout << c->getSortName(true); QString d = c->getDisplayDates(); if (d != "") cout << " (" << d << ")"; if (!c->nationality().empty() || c->period() != "") { cout << " ["; bool first = true; foreach (QString n, c->nationality()) { if (!first) cout << "/"; cout << n; first = false; } if (c->period() != "") { if (!first) cout << ", "; cout << c->period(); } cout << "]"; } if (c->gender() != "") { cout << " *" << c->gender(); } if (!worksMap[c].empty()) { cout << " [" << worksMap[c].size() << " work(s)]"; } cout << endl; foreach (QString a, c->aliases()) { cout << " - " << a << endl; } if (c->remarks() != "") { cout << " " << c->remarks() << endl; } foreach (Document *d, c->pages()) { cout << d->siteName() << " -> " << d->uri() << endl; } foreach (Uri u, c->otherURIs()) { cout << "Same as " << u << endl; } } void showBrief(Composer *c) { cout << c->property("uri").value<Uri>() << endl; cout << c->getSortName(false); QString d = c->getDisplayDates(); if (d != "") cout << " (" << d << ")"; if (!c->nationality().empty() || c->period() != "") { cout << " ["; bool first = true; foreach (QString n, c->nationality()) { if (!first) cout << "/"; cout << n; first = false; } if (c->period() != "") { if (!first) cout << " "; cout << c->period(); } cout << "]"; } if (c->gender() != "") { cout << " *" << c->gender(); } if (!worksMap[c].empty()) { cout << " [" << worksMap[c].size() << " work(s)]"; } cout << endl; } void listBrief(QList<Composer *> composers) { QMultiMap<QString, Composer *> sorted; foreach (Composer *c, composers) { sorted.insert(c->getSortName(false), c); } foreach (Composer *c, sorted) { showBrief(c); } } void listUris(QList<Composer *> composers) { QMultiMap<Uri, Composer *> sorted; foreach (Composer *c, composers) { sorted.insert(c->property("uri").value<Uri>(), c); } foreach (Uri uri, sorted.keys()) { cout << uri << endl; } } void showSearchResults(QMultiMap<float, Composer *> matches, int count) { int n = 0; for (QMultiMap<float, Composer *>::const_iterator i = matches.end(); i != matches.begin(); ) { --i; if (i.key() <= 0) continue; cout << endl; if (n == 0) { cout << "Best match:" << endl; } else if (n == 1) { cout << "Other candidate(s):" << endl; } cout << "[" << i.key() << "] "; if (n == 0) show(i.value()); else showBrief(i.value()); if (++n > count) break; } if (n == 0) cout << "No matches" << endl; cout << endl; } void getTrackData(FileSource source, QString &fingerprint, QString &puid, QString &title, QString &maker, AudioFileReader::TagMap &tags) { AudioFileReader *reader = AudioFileReaderFactory::createReader(source); // AudioFileReader *reader = AudioFileReaderFactory::createThreadingReader(source); if (!reader || !reader->isOK()) { cerr << "Failed to open audio file" << endl; return; } title = reader->getTitle(); maker = reader->getMaker(); // cout << "File tag title: " << reader->getTitle() << endl; // cout << "File tag maker: " << reader->getMaker() << endl; cout << "All tags:" << endl; tags = reader->getTags(); for (AudioFileReader::TagMap::const_iterator i = tags.begin(); i != tags.end(); ++i) { cout << i->first << " " << i->second << endl; } PluginLoader *pl = PluginLoader::getInstance(); Plugin *plugin = pl->loadPlugin ("ofa-vamp-plugin:ofa_puid_and_fingerprint", reader->getSampleRate(), PluginLoader::ADAPT_ALL); if (!plugin) { cerr << "Failed to load OFA Vamp plugin" << endl; return; } // 135 seconds... well, ok, let's have 136 int secs = 136; int want = int(secs * reader->getSampleRate()); int ch = reader->getChannelCount(); std::vector<SampleBlock> samples; reader->getDeInterleavedFrames(0, want, samples); int have = samples[0].size(); if (!plugin->initialise(ch, have, have)) { cerr << "Failed to initialise(" << ch << "," << have << "," << have << ") plugin" << endl; return; } float **input = new float *[ch]; for (int i = 0; i < ch; ++i) { input[i] = &samples[i][0]; } Plugin::FeatureSet features = plugin->process(input, RealTime::zeroTime); if (!features[0].empty()) { fingerprint = QString::fromStdString(features[0][0].label); } if (!features[1].empty()) { puid = QString::fromStdString(features[1][0].label); } features = plugin->getRemainingFeatures(); if (fingerprint == "" && !features[0].empty()) { fingerprint = QString::fromStdString(features[0][0].label); } if (puid == "" && !features[1].empty()) { puid = QString::fromStdString(features[1][0].label); } delete[] input; delete plugin; delete reader; } float bonusFactor(NamedEntity *e) { // tiny nudge to prefer composers we actually have works for Composer *c = qobject_cast<Composer *>(e); float f = 1.f; int sz = 0; if (c && worksMap.contains(c)) { sz = worksMap[c].size(); while (sz > 0) { f += 0.01; sz = sz / 10; } } return f; } void integrateGuesses(GuessSet &guesses, GuessSet newGuesses) { QHash<NamedEntity *, float> ecmap; foreach (Guess g, guesses) { ecmap[g.entity()] += g.confidence() * bonusFactor(g.entity()); } foreach (Guess g, newGuesses) { if (ecmap.contains(g.entity())) { ecmap[g.entity()] += g.confidence() / 2; } else { ecmap[g.entity()] = g.confidence(); } } guesses.clear(); foreach (NamedEntity *e, ecmap.keys()) { guesses.insert(Guess(ecmap[e], e)); } } void guessFromMaker(QString maker, float scale, GuessSet &guesses) { if (maker == "") return; // cerr << "guessFromMaker: " << maker << endl; GuessSet myGuesses; if (composerAliases.contains(maker)) { QList<Composer *> matching = composerAliases.values(maker); foreach (Composer *c, matching) { myGuesses.insert(Guess(10 * scale, c)); } } else { ComposerFullTextMatcher matcher(allComposers); GuessList gl(matcher.match(maker, 5, 0.5)); if (!gl.empty()) { foreach (Guess guess, gl) { myGuesses.insert(Guess(guess.confidence() * scale, guess.entity())); } } } integrateGuesses(guesses, myGuesses); } void guessFromMakerTag(AudioFileReader::TagMap tags, QString tag, float scale, GuessSet &guesses) { if (tags.find(tag) != tags.end()) { guessFromMaker(tags[tag], scale, guesses); } } void guessFromTitle(QString title, float scale, GuessSet &guesses) { QStringList bits = title.split(QRegExp("[:,_-]"), QString::SkipEmptyParts); if (bits.size() > 1) { guessFromMaker(bits.first(), scale, guesses); } } void guessFromTitleTag(AudioFileReader::TagMap tags, QString tag, float scale, GuessSet &guesses) { if (tags.find(tag) != tags.end()) { guessFromTitle(tags[tag], scale, guesses); } } void guessFromFilename(QString filename, float scale, GuessSet &guesses) { cerr << "guessFromFilename: " << filename << endl; QString dirpart = QFileInfo(filename).path(); QStringList dirbits = dirpart.split("/", QString::SkipEmptyParts); dirbits = dirbits.last() .replace(QRegExp("^\\d+"), "") .split(QRegExp("[^\\w]"), QString::SkipEmptyParts); if (!dirbits.empty()) { guessFromMaker(dirbits.first(), scale, guesses); } QString filepart = QFileInfo(filename).fileName().replace(QRegExp("\\d+"), ""); QStringList filebits = filepart.split(QRegExp("[^\\w]"), QString::SkipEmptyParts); if (!filebits.empty()) { guessFromMaker(filebits.first(), scale, guesses); } } void guessWorkFromTitleByCatalogue(QString title, float scale, Composer *composer, GuessSet &guesses) { if (title == "") return; WorkCatalogueMatcher matcher(composer ? worksMap.value(composer) : allWorks); GuessList gl(matcher.match(title, 0)); if (!gl.empty()) { foreach (Guess guess, gl) { guesses.insert(Guess(guess.confidence() * scale, guess.entity())); } } } void guessWorkFromTitle(QString title, float scale, Composer *composer, GuessSet &guesses) { if (title == "") return; WorkTitleMatcher matcher(composer ? worksMap.value(composer) : allWorks); GuessList gl(matcher.match(title, 0)); if (!gl.empty()) { foreach (Guess guess, gl) { guesses.insert(Guess(guess.confidence() * scale, guess.entity())); } } } void guessWorkFromTitleTag(AudioFileReader::TagMap tags, QString tag, float scale, Composer *composer, GuessSet &guesses) { cerr << "guessWorkFromTitleTag: " << tag << endl; if (tags.find(tag) != tags.end()) { cerr << "guessWorkFromTitleTag: tag is " << tags[tag] << endl; GuessSet myGuesses; guessWorkFromTitle(tags[tag], scale, composer, myGuesses); integrateGuesses(guesses, myGuesses); myGuesses.clear(); guessWorkFromTitle(tags[tag], scale, composer, myGuesses); integrateGuesses(guesses, myGuesses); } } void guessWorkFromFilenameByCatalogue(QString filename, float scale, Composer *composer, GuessSet &guesses) { cerr << "guessWorkFromFilename: " << filename << endl; QString dirpart = QFileInfo(filename).path(); QStringList dirbits = dirpart.split("/", QString::SkipEmptyParts); if (!dirbits.empty()) { guessWorkFromTitleByCatalogue(dirbits.last(), scale * 0.7, composer, guesses); } QString filepart = QFileInfo(filename).fileName().replace (QRegExp("\\.[^\\.]*"), "").replace(QRegExp("^\\d+[^\\w]+"), ""); guessWorkFromTitleByCatalogue(filepart, scale, composer, guesses); } void guessWorkFromFilenameByTitle(QString filename, float scale, Composer *composer, GuessSet &guesses) { cerr << "guessWorkFromFilename: " << filename << endl; QString dirpart = QFileInfo(filename).path(); QStringList dirbits = dirpart.split("/", QString::SkipEmptyParts); if (!dirbits.empty()) { guessWorkFromTitle(dirbits.last(), scale * 0.7, composer, guesses); } QString filepart = QFileInfo(filename).fileName().replace (QRegExp("\\.[^\\.]*"), "").replace(QRegExp("^\\d+[^\\w]+"), ""); guessWorkFromTitle(filepart, scale, composer, guesses); } Signal * guess(QString track) { cout << endl; cout << "Guessing composer for: " << track << endl; // cerr << "Creating Signal object..."; FileSource fs(track); Signal *tf = new Signal; tf->addAvailableAs(new AudioFile(fs)); // cerr << "done" << endl; // cerr << "hash = " << tf->hash() << endl; QString fingerprint, puid, maker, title; AudioFileReader::TagMap tags; //!!! bad api!: getTrackData(fs, fingerprint, puid, title, maker, tags); cout << "fingerprint: " << fingerprint.toStdString() << ", puid: " << puid.toStdString() << endl; GuessSet guesses; guessFromMakerTag(tags, "TCOM", 1.0, guesses); guessFromMakerTag(tags, "COMPOSER", 1.0, guesses); if (guesses.empty() || guesses.begin()->confidence() < 0.4) { guessFromMakerTag(tags, "TOPE", 0.8, guesses); guessFromMakerTag(tags, "TPE1", 0.8, guesses); guessFromMakerTag(tags, "ARTIST", 0.9, guesses); guessFromMakerTag(tags, "PERFORMER", 0.8, guesses); guessFromTitleTag(tags, "TIT1", 0.4, guesses); guessFromTitleTag(tags, "TIT2", 0.5, guesses); guessFromTitleTag(tags, "TALB", 0.5, guesses); guessFromTitleTag(tags, "TIT3", 0.3, guesses); guessFromTitleTag(tags, "TITLE", 0.5, guesses); guessFromTitleTag(tags, "ALBUM", 0.5, guesses); } if (tags.find("MUSICBRAINZ_ARTISTID") != tags.end()) { QString id = tags["MUSICBRAINZ_ARTISTID"]; Uri mbzUri = Uri("http://dbtune.org/musicbrainz/resource/artist/" + id); cout << "MBZ id found: " << id << endl; if (composerUris.contains(mbzUri)) { guesses.insert(Guess(2.0, composerUris[mbzUri])); } } cerr << "Composer guesses:" << endl; foreach (Guess g, guesses) { cerr << "[" << g.confidence() << "] " << g.entity()->uri() << endl; } float bc = 0.f; QString best; if (!guesses.empty()) { Guess bg = *guesses.begin(); best = bg.entity()->name(); bc = bg.confidence(); } guessFromFilename(track, 0.5, guesses); float bc2 = 0.f; QString best2; if (!guesses.empty()) { Guess bg = *guesses.begin(); best2 = bg.entity()->name(); bc2 = bg.confidence(); } // If we have only one confident composer guess, consider only // works from that composer (really this should permit considering // works from all confident composers) Composer *confidentComposer = 0; if (bc2 > 0.5) { confidentComposer = qobject_cast<Composer *>(guesses.begin()->entity()); } QString bestTitle; GuessSet workGuesses; if (tags["TIT2"] != "") { bestTitle = tags["TIT2"]; guessWorkFromTitleTag(tags, "TIT2", 0.5, confidentComposer, workGuesses); } if (tags["TITLE"] != "") { bestTitle = tags["TITLE"]; guessWorkFromTitleTag(tags, "TITLE", 0.5, confidentComposer, workGuesses); } if (workGuesses.empty()) { guessWorkFromTitleTag(tags, "TIT1", 0.2, confidentComposer, workGuesses); guessWorkFromTitleTag(tags, "TALB", 0.2, confidentComposer, workGuesses); guessWorkFromTitleTag(tags, "TIT3", 0.1, confidentComposer, workGuesses); guessWorkFromTitleTag(tags, "ALBUM", 0.4, confidentComposer, workGuesses); } if (workGuesses.empty() || workGuesses.begin()->confidence() < 0.3) { guessWorkFromFilenameByCatalogue(track, 0.4, confidentComposer, workGuesses); } if (workGuesses.empty()) { guessWorkFromFilenameByTitle(track, 0.3, confidentComposer, workGuesses); } cerr << "Work guesses:" << endl; foreach (Guess g, workGuesses) { cerr << "[" << g.confidence() << "] " << g.entity()->uri() << endl; } GuessSet consistentComposers; GuessSet consistentWorks; foreach (Guess wg, workGuesses) { Work *w = qobject_cast<Work *>(wg.entity()); if (!w || !w->getComposer()) continue; Composer *wc = w->getComposer(); foreach (Guess g, guesses) { if (g.entity() == wc) { consistentComposers.insert(g); consistentWorks.insert(wg); } } } cerr << "Consistent composer guesses:" << endl; foreach (Guess g, consistentComposers) { cerr << "[" << g.confidence() << "] " << g.entity()->uri() << endl; } cerr << "Consistent work guesses:" << endl; foreach (Guess g, consistentWorks) { cerr << "[" << g.confidence() << "] " << g.entity()->uri() << endl; } float bc3 = bc2; QString best3 = best2; QString work; Work *bestWork = 0; if (!consistentWorks.empty()) { Guess bg = *consistentWorks.begin(); bestWork = qobject_cast<Work *>(bg.entity()); if (bestWork) { bc3 = bg.confidence(); best3 = bestWork->getComposerName(); work = bestWork->getDisplayName(); } } cout << track << "|" << best << "|" << bc << "|" << best2 << "|" << bc2 << "|" << best3 << "|" << bc3 << "|" << work << "|" << bestTitle << endl; tf->setOfaFingerprint(fingerprint); tf->setPuid(puid); tf->setComposer(confidentComposer); tf->setWork(bestWork); return tf; } int main(int argc, char **argv) { //!!! N.B. On Windows this will take the profile path over the //!!! home drive path -- we don't want that QString featuresRelPath(".classical-rdf/features"); if (!QDir(QDir::home().filePath(featuresRelPath)).exists() && !QDir::home().mkpath(featuresRelPath)) { std::cerr << "Features directory $HOME/" << featuresRelPath.toStdString() << " does not exist and creation failed" << std::endl; return 2; } QCoreApplication::setApplicationName("classical-rdf"); FeatureFileIndex *ffi = FeatureFileIndex::getInstance(); QStringList args; for (int i = 1; i < argc; ++i) { args.push_back(argv[i]); } BasicStore bs; if (!args.empty()) { foreach (QString track, args) { FileSource fs(track); AudioFile af(fs); ffi->loadFor(&af, &bs); } } std::cerr << "Dumping out our local store to local2.ttl" << std::endl; bs.save("local2.ttl"); /* BasicStore *index = new BasicStore; QDir features(QDir::home().filePath(featuresRelPath)); features.setFilter(QDir::Files); for (unsigned int i = 0; i < features.count(); ++i) { QFileInfo fi(features.filePath(features[i])); if (fi.isFile() && fi.isReadable()) { std::cout << fi.fileName().toStdString() << std::endl; try { QUrl fileUrl(QUrl::fromLocalFile(fi.filePath())); BasicStore *b = BasicStore::load(fileUrl); Triples ts = b->match (Triple(Node(), "a", Uri("http://purl.org/ontology/mo/AudioFile"))); foreach (Triple t, ts) { std::cout << "Audio file: <" << t.a.value.toStdString() << ">" << std::endl; index->add(Triple(Uri(fileUrl), "a", Uri("http://xmlns.com/foaf/0.1/Document"))); index->add(Triple(Uri(fileUrl), Uri("http://xmlns.com/foaf/0.1/primaryTopic"), t.a)); } } catch (...) { } } } index->save("index.ttl"); */ /* if (argc < 3) usage(argv[0]); QString inRDFFileName = argv[1]; QString command = argv[2]; QStringList args; for (int i = 3; i < argc; ++i) { args.push_back(argv[i]); } //!!! unit test! int c = Work::compareCatalogueNumberTexts("Op. 1 no 4", "Op. 3 no 2"); std::cerr << c << std::endl; c = Work::compareCatalogueNumberTexts("1 no 4", "3 no 2"); std::cerr << c << std::endl; c = Work::compareCatalogueNumberTexts("4 no 2", "3 no 2"); std::cerr << c << std::endl; c = Work::compareCatalogueNumberTexts("Opus 4 no 2", "3 no 2"); std::cerr << c << std::endl; c = Work::compareCatalogueNumberTexts("Op 141", "K. 21"); std::cerr << c << std::endl; c = Work::compareCatalogueNumberTexts("Op 14", "K. 21"); std::cerr << c << std::endl; c = Work::compareCatalogueNumberTexts("Op 6a", "Op 6"); std::cerr << c << std::endl; c = Work::compareCatalogueNumberTexts("Op 6a", "Op 6b"); std::cerr << c << std::endl; c = Work::compareCatalogueNumberTexts("Op 6a", "Op 7"); std::cerr << c << std::endl; c = Work::compareCatalogueNumberTexts("Hob XXIIId:Es1", "Hob XXII:B04"); std::cerr << c << std::endl; BasicStore *store = new BasicStore(); store->setBaseUri(Uri("http://dbtune.org/classical/resource/")); ObjectLoader *loader = new ObjectLoader(store); TypeMapping tm; TypeRegistrar::registerTypes(); TypeRegistrar::addMappings(store, &tm); loader->setTypeMapping(tm); if (!load(store, inRDFFileName)) { cerr << "Failed to load data source" << endl; return 1; } cerr << "Imported RDF data, mapping to objects..."; QObjectList objects = loader->loadAll(); cerr << " done" << endl; delete loader; foreach (QObject *o, objects) { Composer *c = qobject_cast<Composer *>(o); if (c) { allComposers.push_back(c); composerAliases.insert(c->name(), c); foreach (QString alias, c->aliases()) { composerAliases.insert(alias, c); } composerUris.insert(c->property("uri").value<Uri>(), c); foreach (Uri otherUri, c->otherURIs()) { composerUris.insert(otherUri, c); } } } QList<Work *> works; foreach (QObject *o, objects) { Work *w = qobject_cast<Work *>(o); if (w) works.push_back(w); } foreach (Work *w, works) { allWorks.push_back(w); Composition *c = w->composition(); if (c) { Composer *cp = c->composer(); if (cp) worksMap[cp].push_back(w); } } BasicStore localStore; TypeRegistrar::addMappings(&localStore, &tm); ObjectStorer *localStorer = new ObjectStorer(&localStore); localStorer->setTypeMapping(tm); // localStorer->setFollowPolicy(ObjectStorer::FollowObjectProperties); if (command == "guess") { if (args.empty()) usage(argv[0]); foreach (QString track, args) { Signal *tf = guess(track); localStorer->store(tf); } } delete localStorer; localStore.save("local.ttl"); delete store; */ TempDirectory::getInstance()->cleanup(); }