Chris@32
|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
|
Chris@32
|
2
|
Chris@32
|
3 #include "Objects.h"
|
Chris@32
|
4 #include "TypeRegistrar.h"
|
Chris@44
|
5 #include "FeatureFileIndex.h"
|
Chris@32
|
6
|
Chris@32
|
7 #include <dataquay/BasicStore.h>
|
Chris@32
|
8 #include <dataquay/TransactionalStore.h>
|
Chris@32
|
9 #include <dataquay/RDFException.h>
|
Chris@32
|
10 #include <dataquay/objectmapper/ObjectLoader.h>
|
Chris@32
|
11 #include <dataquay/objectmapper/ObjectStorer.h>
|
Chris@32
|
12 #include <dataquay/objectmapper/ObjectMapper.h>
|
Chris@32
|
13 #include <dataquay/objectmapper/TypeMapping.h>
|
Chris@32
|
14
|
Chris@32
|
15 #include "data/fileio/AudioFileReaderFactory.h"
|
Chris@32
|
16 #include "data/fileio/AudioFileReader.h"
|
Chris@33
|
17 #include "base/TempDirectory.h"
|
Chris@33
|
18
|
Chris@33
|
19 #include "Matcher.h"
|
Chris@32
|
20
|
Chris@32
|
21 #include <vamp-hostsdk/PluginLoader.h>
|
Chris@32
|
22
|
Chris@32
|
23 #include <QMultiMap>
|
Chris@32
|
24 #include <QFileInfo>
|
Chris@44
|
25 #include <QDir>
|
Chris@44
|
26 #include <QCoreApplication>
|
Chris@32
|
27
|
Chris@32
|
28 #include <iostream>
|
Chris@32
|
29
|
Chris@32
|
30 using namespace Dataquay;
|
Chris@32
|
31 using namespace ClassicalData;
|
Chris@32
|
32 using namespace std;
|
Chris@32
|
33 using namespace Vamp;
|
Chris@32
|
34 using namespace Vamp::HostExt;
|
Chris@53
|
35 /*
|
Chris@32
|
36 ostream &operator<<(ostream &target, const QString &str)
|
Chris@32
|
37 {
|
Chris@32
|
38 return target << str.toLocal8Bit().data();
|
Chris@32
|
39 }
|
Chris@32
|
40
|
Chris@32
|
41 ostream &operator<<(ostream &target, const QUrl &u)
|
Chris@32
|
42 {
|
Chris@32
|
43 return target << "<" << u.toString() << ">";
|
Chris@32
|
44 }
|
Chris@53
|
45 */
|
Chris@32
|
46
|
Chris@32
|
47 bool
|
Chris@32
|
48 load(BasicStore *store, QString fileName)
|
Chris@32
|
49 {
|
Chris@32
|
50 QUrl url = QUrl::fromLocalFile(fileName);
|
Chris@32
|
51
|
Chris@32
|
52 cerr << "Importing from URL " << url << " ...";
|
Chris@32
|
53 try {
|
Chris@32
|
54 store->import(url, BasicStore::ImportPermitDuplicates);
|
Chris@32
|
55 } catch (RDFException e) {
|
Chris@32
|
56 cerr << " retrying with explicit ntriples type...";
|
Chris@32
|
57 try {
|
Chris@32
|
58 store->import(url, BasicStore::ImportPermitDuplicates, "ntriples");
|
Chris@32
|
59 } catch (RDFException e) {
|
Chris@32
|
60 cerr << "failed" << endl;
|
Chris@32
|
61 cerr << "Import failed: " << e.what() << endl;
|
Chris@32
|
62 return false;
|
Chris@32
|
63 }
|
Chris@32
|
64 }
|
Chris@32
|
65
|
Chris@32
|
66 cerr << " done" << endl;
|
Chris@32
|
67 return true;
|
Chris@32
|
68 }
|
Chris@32
|
69
|
Chris@32
|
70 void
|
Chris@32
|
71 usage(char *name)
|
Chris@32
|
72 {
|
Chris@32
|
73 int s = 0;
|
Chris@32
|
74 for (int i = 0; name[i]; ++i) if (name[i] == '/') s = i + 1;
|
Chris@32
|
75 name = name + s;
|
Chris@32
|
76 cerr << "Usage:" << endl;
|
Chris@32
|
77 cerr << " " << name << " <input-rdf-file> guess <track.wav> [<track.wav> ...]" << endl;
|
Chris@32
|
78 exit(-1);
|
Chris@32
|
79 }
|
Chris@32
|
80
|
Chris@32
|
81 static QList<Composer *> allComposers;
|
Chris@33
|
82 static QHash<QString, Composer *> composerAliases;
|
Chris@33
|
83 static QHash<Uri, Composer *> composerUris;
|
Chris@37
|
84 static QMap<Composer *, QList<Work *> > worksMap;
|
Chris@34
|
85 static QList<Work *> allWorks;
|
Chris@32
|
86
|
Chris@32
|
87 void
|
Chris@32
|
88 show(Composer *c)
|
Chris@32
|
89 {
|
Chris@32
|
90 cout << c->property("uri").value<Uri>() << endl;
|
Chris@32
|
91 cout << c->getSortName(true);
|
Chris@32
|
92 QString d = c->getDisplayDates();
|
Chris@32
|
93 if (d != "") cout << " (" << d << ")";
|
Chris@32
|
94 if (!c->nationality().empty() || c->period() != "") {
|
Chris@32
|
95 cout << " [";
|
Chris@32
|
96 bool first = true;
|
Chris@32
|
97 foreach (QString n, c->nationality()) {
|
Chris@32
|
98 if (!first) cout << "/";
|
Chris@32
|
99 cout << n;
|
Chris@32
|
100 first = false;
|
Chris@32
|
101 }
|
Chris@32
|
102 if (c->period() != "") {
|
Chris@32
|
103 if (!first) cout << ", ";
|
Chris@32
|
104 cout << c->period();
|
Chris@32
|
105 }
|
Chris@32
|
106 cout << "]";
|
Chris@32
|
107 }
|
Chris@32
|
108 if (c->gender() != "") {
|
Chris@32
|
109 cout << " *" << c->gender();
|
Chris@32
|
110 }
|
Chris@32
|
111 if (!worksMap[c].empty()) {
|
Chris@32
|
112 cout << " [" << worksMap[c].size() << " work(s)]";
|
Chris@32
|
113 }
|
Chris@32
|
114 cout << endl;
|
Chris@32
|
115 foreach (QString a, c->aliases()) {
|
Chris@32
|
116 cout << " - " << a << endl;
|
Chris@32
|
117 }
|
Chris@32
|
118 if (c->remarks() != "") {
|
Chris@32
|
119 cout << " " << c->remarks() << endl;
|
Chris@32
|
120 }
|
Chris@32
|
121 foreach (Document *d, c->pages()) {
|
Chris@32
|
122 cout << d->siteName() << " -> " << d->uri() << endl;
|
Chris@32
|
123 }
|
Chris@32
|
124 foreach (Uri u, c->otherURIs()) {
|
Chris@32
|
125 cout << "Same as " << u << endl;
|
Chris@32
|
126 }
|
Chris@32
|
127 }
|
Chris@32
|
128
|
Chris@32
|
129 void
|
Chris@32
|
130 showBrief(Composer *c)
|
Chris@32
|
131 {
|
Chris@32
|
132 cout << c->property("uri").value<Uri>() << endl;
|
Chris@32
|
133 cout << c->getSortName(false);
|
Chris@32
|
134 QString d = c->getDisplayDates();
|
Chris@32
|
135 if (d != "") cout << " (" << d << ")";
|
Chris@32
|
136 if (!c->nationality().empty() || c->period() != "") {
|
Chris@32
|
137 cout << " [";
|
Chris@32
|
138 bool first = true;
|
Chris@32
|
139 foreach (QString n, c->nationality()) {
|
Chris@32
|
140 if (!first) cout << "/";
|
Chris@32
|
141 cout << n;
|
Chris@32
|
142 first = false;
|
Chris@32
|
143 }
|
Chris@32
|
144 if (c->period() != "") {
|
Chris@32
|
145 if (!first) cout << " ";
|
Chris@32
|
146 cout << c->period();
|
Chris@32
|
147 }
|
Chris@32
|
148 cout << "]";
|
Chris@32
|
149 }
|
Chris@32
|
150 if (c->gender() != "") {
|
Chris@32
|
151 cout << " *" << c->gender();
|
Chris@32
|
152 }
|
Chris@32
|
153 if (!worksMap[c].empty()) {
|
Chris@32
|
154 cout << " [" << worksMap[c].size() << " work(s)]";
|
Chris@32
|
155 }
|
Chris@32
|
156 cout << endl;
|
Chris@32
|
157 }
|
Chris@32
|
158
|
Chris@32
|
159 void
|
Chris@32
|
160 listBrief(QList<Composer *> composers)
|
Chris@32
|
161 {
|
Chris@32
|
162 QMultiMap<QString, Composer *> sorted;
|
Chris@32
|
163 foreach (Composer *c, composers) {
|
Chris@32
|
164 sorted.insert(c->getSortName(false), c);
|
Chris@32
|
165 }
|
Chris@32
|
166 foreach (Composer *c, sorted) {
|
Chris@32
|
167 showBrief(c);
|
Chris@32
|
168 }
|
Chris@32
|
169 }
|
Chris@32
|
170
|
Chris@32
|
171 void
|
Chris@32
|
172 listUris(QList<Composer *> composers)
|
Chris@32
|
173 {
|
Chris@32
|
174 QMultiMap<Uri, Composer *> sorted;
|
Chris@32
|
175 foreach (Composer *c, composers) {
|
Chris@32
|
176 sorted.insert(c->property("uri").value<Uri>(), c);
|
Chris@32
|
177 }
|
Chris@32
|
178 foreach (Uri uri, sorted.keys()) {
|
Chris@32
|
179 cout << uri << endl;
|
Chris@32
|
180 }
|
Chris@32
|
181 }
|
Chris@32
|
182
|
Chris@32
|
183 void
|
Chris@32
|
184 showSearchResults(QMultiMap<float, Composer *> matches, int count)
|
Chris@32
|
185 {
|
Chris@32
|
186 int n = 0;
|
Chris@32
|
187 for (QMultiMap<float, Composer *>::const_iterator i = matches.end();
|
Chris@32
|
188 i != matches.begin(); ) {
|
Chris@32
|
189 --i;
|
Chris@32
|
190 if (i.key() <= 0) continue;
|
Chris@32
|
191 cout << endl;
|
Chris@32
|
192 if (n == 0) {
|
Chris@32
|
193 cout << "Best match:" << endl;
|
Chris@32
|
194 } else if (n == 1) {
|
Chris@32
|
195 cout << "Other candidate(s):" << endl;
|
Chris@32
|
196 }
|
Chris@32
|
197 cout << "[" << i.key() << "] ";
|
Chris@32
|
198 if (n == 0) show(i.value());
|
Chris@32
|
199 else showBrief(i.value());
|
Chris@32
|
200 if (++n > count) break;
|
Chris@32
|
201 }
|
Chris@32
|
202 if (n == 0) cout << "No matches" << endl;
|
Chris@32
|
203 cout << endl;
|
Chris@32
|
204 }
|
Chris@32
|
205
|
Chris@32
|
206 void
|
Chris@33
|
207 getTrackData(FileSource source, QString &fingerprint, QString &puid,
|
Chris@33
|
208 QString &title, QString &maker, AudioFileReader::TagMap &tags)
|
Chris@32
|
209 {
|
Chris@43
|
210 AudioFileReader *reader = AudioFileReaderFactory::createReader(source);
|
Chris@43
|
211 // AudioFileReader *reader = AudioFileReaderFactory::createThreadingReader(source);
|
Chris@32
|
212 if (!reader || !reader->isOK()) {
|
Chris@32
|
213 cerr << "Failed to open audio file" << endl;
|
Chris@32
|
214 return;
|
Chris@32
|
215 }
|
Chris@32
|
216
|
Chris@33
|
217 title = reader->getTitle();
|
Chris@33
|
218 maker = reader->getMaker();
|
Chris@34
|
219 // cout << "File tag title: " << reader->getTitle() << endl;
|
Chris@34
|
220 // cout << "File tag maker: " << reader->getMaker() << endl;
|
Chris@32
|
221
|
Chris@32
|
222 cout << "All tags:" << endl;
|
Chris@33
|
223 tags = reader->getTags();
|
Chris@32
|
224 for (AudioFileReader::TagMap::const_iterator i = tags.begin();
|
Chris@32
|
225 i != tags.end(); ++i) {
|
Chris@32
|
226 cout << i->first << " " << i->second << endl;
|
Chris@32
|
227 }
|
Chris@32
|
228
|
Chris@32
|
229 PluginLoader *pl = PluginLoader::getInstance();
|
Chris@32
|
230 Plugin *plugin = pl->loadPlugin
|
Chris@32
|
231 ("ofa-vamp-plugin:ofa_puid_and_fingerprint", reader->getSampleRate(), PluginLoader::ADAPT_ALL);
|
Chris@32
|
232 if (!plugin) {
|
Chris@32
|
233 cerr << "Failed to load OFA Vamp plugin" << endl;
|
Chris@32
|
234 return;
|
Chris@32
|
235 }
|
Chris@32
|
236
|
Chris@32
|
237 // 135 seconds... well, ok, let's have 136
|
Chris@32
|
238 int secs = 136;
|
Chris@32
|
239
|
Chris@32
|
240 int want = int(secs * reader->getSampleRate());
|
Chris@32
|
241 int ch = reader->getChannelCount();
|
Chris@32
|
242 std::vector<SampleBlock> samples;
|
Chris@32
|
243 reader->getDeInterleavedFrames(0, want, samples);
|
Chris@32
|
244 int have = samples[0].size();
|
Chris@32
|
245 if (!plugin->initialise(ch, have, have)) {
|
Chris@32
|
246 cerr << "Failed to initialise(" << ch << "," << have << "," << have << ") plugin" << endl;
|
Chris@32
|
247 return;
|
Chris@32
|
248 }
|
Chris@32
|
249
|
Chris@32
|
250 float **input = new float *[ch];
|
Chris@32
|
251 for (int i = 0; i < ch; ++i) {
|
Chris@32
|
252 input[i] = &samples[i][0];
|
Chris@32
|
253 }
|
Chris@32
|
254 Plugin::FeatureSet features = plugin->process(input, RealTime::zeroTime);
|
Chris@33
|
255 if (!features[0].empty()) {
|
Chris@33
|
256 fingerprint = QString::fromStdString(features[0][0].label);
|
Chris@33
|
257 }
|
Chris@33
|
258 if (!features[1].empty()) {
|
Chris@33
|
259 puid = QString::fromStdString(features[1][0].label);
|
Chris@33
|
260 }
|
Chris@32
|
261 features = plugin->getRemainingFeatures();
|
Chris@33
|
262 if (fingerprint == "" && !features[0].empty()) {
|
Chris@33
|
263 fingerprint = QString::fromStdString(features[0][0].label);
|
Chris@33
|
264 }
|
Chris@33
|
265 if (puid == "" && !features[1].empty()) {
|
Chris@33
|
266 puid = QString::fromStdString(features[1][0].label);
|
Chris@33
|
267 }
|
Chris@34
|
268 delete[] input;
|
Chris@34
|
269 delete plugin;
|
Chris@34
|
270 delete reader;
|
Chris@34
|
271 }
|
Chris@34
|
272
|
Chris@34
|
273 float
|
Chris@34
|
274 bonusFactor(NamedEntity *e)
|
Chris@34
|
275 {
|
Chris@34
|
276 // tiny nudge to prefer composers we actually have works for
|
Chris@34
|
277 Composer *c = qobject_cast<Composer *>(e);
|
Chris@34
|
278 float f = 1.f;
|
Chris@34
|
279 int sz = 0;
|
Chris@34
|
280 if (c && worksMap.contains(c)) {
|
Chris@34
|
281 sz = worksMap[c].size();
|
Chris@34
|
282 while (sz > 0) {
|
Chris@34
|
283 f += 0.01;
|
Chris@34
|
284 sz = sz / 10;
|
Chris@34
|
285 }
|
Chris@34
|
286 }
|
Chris@34
|
287 return f;
|
Chris@34
|
288 }
|
Chris@34
|
289
|
Chris@34
|
290 void
|
Chris@34
|
291 integrateGuesses(GuessSet &guesses, GuessSet newGuesses)
|
Chris@34
|
292 {
|
Chris@34
|
293 QHash<NamedEntity *, float> ecmap;
|
Chris@34
|
294 foreach (Guess g, guesses) {
|
Chris@34
|
295 ecmap[g.entity()] += g.confidence() * bonusFactor(g.entity());
|
Chris@34
|
296 }
|
Chris@34
|
297 foreach (Guess g, newGuesses) {
|
Chris@34
|
298 if (ecmap.contains(g.entity())) {
|
Chris@34
|
299 ecmap[g.entity()] += g.confidence() / 2;
|
Chris@34
|
300 } else {
|
Chris@34
|
301 ecmap[g.entity()] = g.confidence();
|
Chris@34
|
302 }
|
Chris@34
|
303 }
|
Chris@34
|
304 guesses.clear();
|
Chris@34
|
305 foreach (NamedEntity *e, ecmap.keys()) {
|
Chris@34
|
306 guesses.insert(Guess(ecmap[e], e));
|
Chris@34
|
307 }
|
Chris@34
|
308 }
|
Chris@34
|
309
|
Chris@34
|
310 void
|
Chris@34
|
311 guessFromMaker(QString maker, float scale, GuessSet &guesses)
|
Chris@34
|
312 {
|
Chris@34
|
313 if (maker == "") return;
|
Chris@34
|
314 // cerr << "guessFromMaker: " << maker << endl;
|
Chris@34
|
315 GuessSet myGuesses;
|
Chris@34
|
316 if (composerAliases.contains(maker)) {
|
Chris@34
|
317 QList<Composer *> matching = composerAliases.values(maker);
|
Chris@34
|
318 foreach (Composer *c, matching) {
|
Chris@34
|
319 myGuesses.insert(Guess(10 * scale, c));
|
Chris@34
|
320 }
|
Chris@34
|
321 } else {
|
Chris@34
|
322 ComposerFullTextMatcher matcher(allComposers);
|
Chris@34
|
323 GuessList gl(matcher.match(maker, 5, 0.5));
|
Chris@34
|
324 if (!gl.empty()) {
|
Chris@34
|
325 foreach (Guess guess, gl) {
|
Chris@34
|
326 myGuesses.insert(Guess(guess.confidence() * scale, guess.entity()));
|
Chris@34
|
327 }
|
Chris@34
|
328 }
|
Chris@34
|
329 }
|
Chris@34
|
330 integrateGuesses(guesses, myGuesses);
|
Chris@34
|
331 }
|
Chris@34
|
332
|
Chris@34
|
333 void
|
Chris@34
|
334 guessFromMakerTag(AudioFileReader::TagMap tags, QString tag, float scale, GuessSet &guesses)
|
Chris@34
|
335 {
|
Chris@34
|
336 if (tags.find(tag) != tags.end()) {
|
Chris@34
|
337 guessFromMaker(tags[tag], scale, guesses);
|
Chris@34
|
338 }
|
Chris@34
|
339 }
|
Chris@34
|
340
|
Chris@34
|
341 void
|
Chris@34
|
342 guessFromTitle(QString title, float scale, GuessSet &guesses)
|
Chris@34
|
343 {
|
Chris@34
|
344 QStringList bits = title.split(QRegExp("[:,_-]"),
|
Chris@34
|
345 QString::SkipEmptyParts);
|
Chris@34
|
346 if (bits.size() > 1) {
|
Chris@34
|
347 guessFromMaker(bits.first(), scale, guesses);
|
Chris@34
|
348 }
|
Chris@34
|
349 }
|
Chris@34
|
350
|
Chris@34
|
351 void
|
Chris@34
|
352 guessFromTitleTag(AudioFileReader::TagMap tags, QString tag, float scale, GuessSet &guesses)
|
Chris@34
|
353 {
|
Chris@34
|
354 if (tags.find(tag) != tags.end()) {
|
Chris@34
|
355 guessFromTitle(tags[tag], scale, guesses);
|
Chris@34
|
356 }
|
Chris@34
|
357 }
|
Chris@34
|
358
|
Chris@34
|
359 void
|
Chris@34
|
360 guessFromFilename(QString filename, float scale, GuessSet &guesses)
|
Chris@34
|
361 {
|
Chris@34
|
362 cerr << "guessFromFilename: " << filename << endl;
|
Chris@37
|
363 QString dirpart = QFileInfo(filename).path();
|
Chris@34
|
364 QStringList dirbits = dirpart.split("/", QString::SkipEmptyParts);
|
Chris@37
|
365 dirbits = dirbits.last()
|
Chris@37
|
366 .replace(QRegExp("^\\d+"), "")
|
Chris@37
|
367 .split(QRegExp("[^\\w]"), QString::SkipEmptyParts);
|
Chris@34
|
368 if (!dirbits.empty()) {
|
Chris@34
|
369 guessFromMaker(dirbits.first(), scale, guesses);
|
Chris@34
|
370 }
|
Chris@34
|
371
|
Chris@34
|
372 QString filepart = QFileInfo(filename).fileName().replace(QRegExp("\\d+"), "");
|
Chris@34
|
373 QStringList filebits = filepart.split(QRegExp("[^\\w]"),
|
Chris@34
|
374 QString::SkipEmptyParts);
|
Chris@34
|
375 if (!filebits.empty()) {
|
Chris@34
|
376 guessFromMaker(filebits.first(), scale, guesses);
|
Chris@34
|
377 }
|
Chris@34
|
378 }
|
Chris@34
|
379
|
Chris@37
|
380 void
|
Chris@37
|
381 guessWorkFromTitleByCatalogue(QString title, float scale,
|
Chris@37
|
382 Composer *composer, GuessSet &guesses)
|
Chris@34
|
383 {
|
Chris@34
|
384 if (title == "") return;
|
Chris@37
|
385 WorkCatalogueMatcher matcher(composer ? worksMap.value(composer) : allWorks);
|
Chris@37
|
386 GuessList gl(matcher.match(title, 0));
|
Chris@37
|
387 if (!gl.empty()) {
|
Chris@37
|
388 foreach (Guess guess, gl) {
|
Chris@37
|
389 guesses.insert(Guess(guess.confidence() * scale, guess.entity()));
|
Chris@34
|
390 }
|
Chris@34
|
391 }
|
Chris@37
|
392 }
|
Chris@37
|
393
|
Chris@37
|
394 void
|
Chris@37
|
395 guessWorkFromTitle(QString title, float scale,
|
Chris@37
|
396 Composer *composer, GuessSet &guesses)
|
Chris@37
|
397 {
|
Chris@37
|
398 if (title == "") return;
|
Chris@37
|
399 WorkTitleMatcher matcher(composer ? worksMap.value(composer) : allWorks);
|
Chris@37
|
400 GuessList gl(matcher.match(title, 0));
|
Chris@37
|
401 if (!gl.empty()) {
|
Chris@37
|
402 foreach (Guess guess, gl) {
|
Chris@37
|
403 guesses.insert(Guess(guess.confidence() * scale, guess.entity()));
|
Chris@35
|
404 }
|
Chris@35
|
405 }
|
Chris@34
|
406 }
|
Chris@34
|
407
|
Chris@34
|
408 void
|
Chris@37
|
409 guessWorkFromTitleTag(AudioFileReader::TagMap tags, QString tag, float scale,
|
Chris@37
|
410 Composer *composer, GuessSet &guesses)
|
Chris@34
|
411 {
|
Chris@40
|
412 cerr << "guessWorkFromTitleTag: " << tag << endl;
|
Chris@40
|
413
|
Chris@34
|
414 if (tags.find(tag) != tags.end()) {
|
Chris@40
|
415 cerr << "guessWorkFromTitleTag: tag is " << tags[tag] << endl;
|
Chris@37
|
416 GuessSet myGuesses;
|
Chris@37
|
417 guessWorkFromTitle(tags[tag], scale, composer, myGuesses);
|
Chris@37
|
418 integrateGuesses(guesses, myGuesses);
|
Chris@37
|
419 myGuesses.clear();
|
Chris@37
|
420 guessWorkFromTitle(tags[tag], scale, composer, myGuesses);
|
Chris@37
|
421 integrateGuesses(guesses, myGuesses);
|
Chris@34
|
422 }
|
Chris@32
|
423 }
|
Chris@32
|
424
|
Chris@33
|
425 void
|
Chris@37
|
426 guessWorkFromFilenameByCatalogue(QString filename, float scale,
|
Chris@37
|
427 Composer *composer, GuessSet &guesses)
|
Chris@35
|
428 {
|
Chris@35
|
429 cerr << "guessWorkFromFilename: " << filename << endl;
|
Chris@37
|
430
|
Chris@37
|
431 QString dirpart = QFileInfo(filename).path();
|
Chris@37
|
432 QStringList dirbits = dirpart.split("/", QString::SkipEmptyParts);
|
Chris@37
|
433 if (!dirbits.empty()) {
|
Chris@37
|
434 guessWorkFromTitleByCatalogue(dirbits.last(), scale * 0.7, composer, guesses);
|
Chris@37
|
435 }
|
Chris@37
|
436
|
Chris@37
|
437 QString filepart = QFileInfo(filename).fileName().replace
|
Chris@37
|
438 (QRegExp("\\.[^\\.]*"), "").replace(QRegExp("^\\d+[^\\w]+"), "");
|
Chris@37
|
439 guessWorkFromTitleByCatalogue(filepart, scale, composer, guesses);
|
Chris@37
|
440 }
|
Chris@37
|
441
|
Chris@37
|
442 void
|
Chris@37
|
443 guessWorkFromFilenameByTitle(QString filename, float scale,
|
Chris@37
|
444 Composer *composer, GuessSet &guesses)
|
Chris@37
|
445 {
|
Chris@37
|
446 cerr << "guessWorkFromFilename: " << filename << endl;
|
Chris@37
|
447
|
Chris@37
|
448 QString dirpart = QFileInfo(filename).path();
|
Chris@37
|
449 QStringList dirbits = dirpart.split("/", QString::SkipEmptyParts);
|
Chris@37
|
450 if (!dirbits.empty()) {
|
Chris@37
|
451 guessWorkFromTitle(dirbits.last(), scale * 0.7, composer, guesses);
|
Chris@37
|
452 }
|
Chris@37
|
453
|
Chris@37
|
454 QString filepart = QFileInfo(filename).fileName().replace
|
Chris@37
|
455 (QRegExp("\\.[^\\.]*"), "").replace(QRegExp("^\\d+[^\\w]+"), "");
|
Chris@37
|
456 guessWorkFromTitle(filepart, scale, composer, guesses);
|
Chris@35
|
457 }
|
Chris@35
|
458
|
Chris@45
|
459 Signal *
|
Chris@33
|
460 guess(QString track)
|
Chris@33
|
461 {
|
Chris@34
|
462 cout << endl;
|
Chris@33
|
463 cout << "Guessing composer for: " << track << endl;
|
Chris@33
|
464
|
Chris@45
|
465 // cerr << "Creating Signal object...";
|
Chris@33
|
466 FileSource fs(track);
|
Chris@45
|
467 Signal *tf = new Signal;
|
Chris@45
|
468 tf->addAvailableAs(new AudioFile(fs));
|
Chris@34
|
469 // cerr << "done" << endl;
|
Chris@34
|
470 // cerr << "hash = " << tf->hash() << endl;
|
Chris@33
|
471
|
Chris@33
|
472 QString fingerprint, puid, maker, title;
|
Chris@33
|
473 AudioFileReader::TagMap tags;
|
Chris@33
|
474 //!!! bad api!:
|
Chris@33
|
475 getTrackData(fs, fingerprint, puid, title, maker, tags);
|
Chris@33
|
476
|
Chris@43
|
477 cout << "fingerprint: " << fingerprint.toStdString() << ", puid: "
|
Chris@43
|
478 << puid.toStdString() << endl;
|
Chris@43
|
479
|
Chris@34
|
480 GuessSet guesses;
|
Chris@34
|
481
|
Chris@34
|
482 guessFromMakerTag(tags, "TCOM", 1.0, guesses);
|
Chris@34
|
483 guessFromMakerTag(tags, "COMPOSER", 1.0, guesses);
|
Chris@34
|
484
|
Chris@34
|
485 if (guesses.empty() || guesses.begin()->confidence() < 0.4) {
|
Chris@34
|
486 guessFromMakerTag(tags, "TOPE", 0.8, guesses);
|
Chris@34
|
487 guessFromMakerTag(tags, "TPE1", 0.8, guesses);
|
Chris@34
|
488
|
Chris@34
|
489 guessFromMakerTag(tags, "ARTIST", 0.9, guesses);
|
Chris@34
|
490 guessFromMakerTag(tags, "PERFORMER", 0.8, guesses);
|
Chris@34
|
491
|
Chris@34
|
492 guessFromTitleTag(tags, "TIT1", 0.4, guesses);
|
Chris@34
|
493 guessFromTitleTag(tags, "TIT2", 0.5, guesses);
|
Chris@34
|
494 guessFromTitleTag(tags, "TALB", 0.5, guesses);
|
Chris@34
|
495 guessFromTitleTag(tags, "TIT3", 0.3, guesses);
|
Chris@34
|
496
|
Chris@34
|
497 guessFromTitleTag(tags, "TITLE", 0.5, guesses);
|
Chris@34
|
498 guessFromTitleTag(tags, "ALBUM", 0.5, guesses);
|
Chris@33
|
499 }
|
Chris@33
|
500
|
Chris@33
|
501 if (tags.find("MUSICBRAINZ_ARTISTID") != tags.end()) {
|
Chris@33
|
502 QString id = tags["MUSICBRAINZ_ARTISTID"];
|
Chris@33
|
503 Uri mbzUri = Uri("http://dbtune.org/musicbrainz/resource/artist/" + id);
|
Chris@33
|
504 cout << "MBZ id found: " << id << endl;
|
Chris@33
|
505 if (composerUris.contains(mbzUri)) {
|
Chris@34
|
506 guesses.insert(Guess(2.0, composerUris[mbzUri]));
|
Chris@33
|
507 }
|
Chris@33
|
508 }
|
Chris@33
|
509
|
Chris@34
|
510 cerr << "Composer guesses:" << endl;
|
Chris@34
|
511 foreach (Guess g, guesses) {
|
Chris@34
|
512 cerr << "[" << g.confidence() << "] " << g.entity()->uri() << endl;
|
Chris@34
|
513 }
|
Chris@34
|
514
|
Chris@34
|
515 float bc = 0.f;
|
Chris@34
|
516 QString best;
|
Chris@34
|
517 if (!guesses.empty()) {
|
Chris@34
|
518 Guess bg = *guesses.begin();
|
Chris@34
|
519 best = bg.entity()->name();
|
Chris@34
|
520 bc = bg.confidence();
|
Chris@34
|
521 }
|
Chris@34
|
522
|
Chris@34
|
523 guessFromFilename(track, 0.5, guesses);
|
Chris@34
|
524
|
Chris@34
|
525 float bc2 = 0.f;
|
Chris@34
|
526 QString best2;
|
Chris@34
|
527 if (!guesses.empty()) {
|
Chris@34
|
528 Guess bg = *guesses.begin();
|
Chris@34
|
529 best2 = bg.entity()->name();
|
Chris@34
|
530 bc2 = bg.confidence();
|
Chris@34
|
531 }
|
Chris@34
|
532
|
Chris@37
|
533 // If we have only one confident composer guess, consider only
|
Chris@37
|
534 // works from that composer (really this should permit considering
|
Chris@37
|
535 // works from all confident composers)
|
Chris@37
|
536 Composer *confidentComposer = 0;
|
Chris@37
|
537 if (bc2 > 0.5) {
|
Chris@37
|
538 confidentComposer = qobject_cast<Composer *>(guesses.begin()->entity());
|
Chris@37
|
539 }
|
Chris@37
|
540
|
Chris@39
|
541 QString bestTitle;
|
Chris@39
|
542
|
Chris@34
|
543 GuessSet workGuesses;
|
Chris@40
|
544 if (tags["TIT2"] != "") {
|
Chris@40
|
545 bestTitle = tags["TIT2"];
|
Chris@40
|
546 guessWorkFromTitleTag(tags, "TIT2", 0.5, confidentComposer, workGuesses);
|
Chris@40
|
547 }
|
Chris@37
|
548 if (tags["TITLE"] != "") {
|
Chris@39
|
549 bestTitle = tags["TITLE"];
|
Chris@37
|
550 guessWorkFromTitleTag(tags, "TITLE", 0.5, confidentComposer, workGuesses);
|
Chris@37
|
551 }
|
Chris@37
|
552 if (workGuesses.empty()) {
|
Chris@37
|
553 guessWorkFromTitleTag(tags, "TIT1", 0.2, confidentComposer, workGuesses);
|
Chris@37
|
554 guessWorkFromTitleTag(tags, "TALB", 0.2, confidentComposer, workGuesses);
|
Chris@37
|
555 guessWorkFromTitleTag(tags, "TIT3", 0.1, confidentComposer, workGuesses);
|
Chris@37
|
556 guessWorkFromTitleTag(tags, "ALBUM", 0.4, confidentComposer, workGuesses);
|
Chris@37
|
557 }
|
Chris@37
|
558 if (workGuesses.empty() || workGuesses.begin()->confidence() < 0.3) {
|
Chris@37
|
559 guessWorkFromFilenameByCatalogue(track, 0.4, confidentComposer, workGuesses);
|
Chris@37
|
560 }
|
Chris@37
|
561 if (workGuesses.empty()) {
|
Chris@37
|
562 guessWorkFromFilenameByTitle(track, 0.3, confidentComposer, workGuesses);
|
Chris@37
|
563 }
|
Chris@33
|
564
|
Chris@34
|
565 cerr << "Work guesses:" << endl;
|
Chris@34
|
566 foreach (Guess g, workGuesses) {
|
Chris@34
|
567 cerr << "[" << g.confidence() << "] " << g.entity()->uri() << endl;
|
Chris@34
|
568 }
|
Chris@34
|
569
|
Chris@34
|
570 GuessSet consistentComposers;
|
Chris@34
|
571 GuessSet consistentWorks;
|
Chris@34
|
572 foreach (Guess wg, workGuesses) {
|
Chris@34
|
573 Work *w = qobject_cast<Work *>(wg.entity());
|
Chris@37
|
574 if (!w || !w->getComposer()) continue;
|
Chris@37
|
575 Composer *wc = w->getComposer();
|
Chris@34
|
576 foreach (Guess g, guesses) {
|
Chris@34
|
577 if (g.entity() == wc) {
|
Chris@34
|
578 consistentComposers.insert(g);
|
Chris@40
|
579 consistentWorks.insert(wg);
|
Chris@34
|
580 }
|
Chris@34
|
581 }
|
Chris@34
|
582 }
|
Chris@34
|
583
|
Chris@34
|
584 cerr << "Consistent composer guesses:" << endl;
|
Chris@34
|
585 foreach (Guess g, consistentComposers) {
|
Chris@34
|
586 cerr << "[" << g.confidence() << "] " << g.entity()->uri() << endl;
|
Chris@34
|
587 }
|
Chris@34
|
588
|
Chris@34
|
589 cerr << "Consistent work guesses:" << endl;
|
Chris@34
|
590 foreach (Guess g, consistentWorks) {
|
Chris@34
|
591 cerr << "[" << g.confidence() << "] " << g.entity()->uri() << endl;
|
Chris@34
|
592 }
|
Chris@34
|
593
|
Chris@34
|
594 float bc3 = bc2;
|
Chris@34
|
595 QString best3 = best2;
|
Chris@34
|
596 QString work;
|
Chris@43
|
597 Work *bestWork = 0;
|
Chris@37
|
598 if (!consistentWorks.empty()) {
|
Chris@37
|
599 Guess bg = *consistentWorks.begin();
|
Chris@43
|
600 bestWork = qobject_cast<Work *>(bg.entity());
|
Chris@43
|
601 if (bestWork) {
|
Chris@37
|
602 bc3 = bg.confidence();
|
Chris@43
|
603 best3 = bestWork->getComposerName();
|
Chris@43
|
604 work = bestWork->getDisplayName();
|
Chris@37
|
605 }
|
Chris@34
|
606 }
|
Chris@34
|
607
|
Chris@39
|
608 cout << track << "|" << best << "|" << bc << "|" << best2 << "|" << bc2 << "|" << best3 << "|" << bc3 << "|" << work << "|" << bestTitle << endl;
|
Chris@43
|
609
|
Chris@43
|
610 tf->setOfaFingerprint(fingerprint);
|
Chris@43
|
611 tf->setPuid(puid);
|
Chris@43
|
612 tf->setComposer(confidentComposer);
|
Chris@43
|
613 tf->setWork(bestWork);
|
Chris@43
|
614 return tf;
|
Chris@33
|
615 }
|
Chris@32
|
616
|
Chris@44
|
617
|
Chris@44
|
618
|
Chris@32
|
619 int
|
Chris@32
|
620 main(int argc, char **argv)
|
Chris@32
|
621 {
|
Chris@44
|
622 //!!! N.B. On Windows this will take the profile path over the
|
Chris@44
|
623 //!!! home drive path -- we don't want that
|
Chris@44
|
624 QString featuresRelPath(".classical-rdf/features");
|
Chris@44
|
625 if (!QDir(QDir::home().filePath(featuresRelPath)).exists() &&
|
Chris@44
|
626 !QDir::home().mkpath(featuresRelPath)) {
|
Chris@44
|
627 std::cerr << "Features directory $HOME/" << featuresRelPath.toStdString()
|
Chris@44
|
628 << " does not exist and creation failed" << std::endl;
|
Chris@44
|
629 return 2;
|
Chris@44
|
630 }
|
Chris@44
|
631
|
Chris@44
|
632 QCoreApplication::setApplicationName("classical-rdf");
|
Chris@44
|
633
|
Chris@44
|
634 FeatureFileIndex *ffi = FeatureFileIndex::getInstance();
|
Chris@44
|
635
|
Chris@45
|
636 QStringList args;
|
Chris@45
|
637 for (int i = 1; i < argc; ++i) {
|
Chris@45
|
638 args.push_back(argv[i]);
|
Chris@45
|
639 }
|
Chris@45
|
640
|
Chris@45
|
641 BasicStore bs;
|
Chris@45
|
642 if (!args.empty()) {
|
Chris@45
|
643 foreach (QString track, args) {
|
Chris@45
|
644 FileSource fs(track);
|
Chris@45
|
645 AudioFile af(fs);
|
Chris@45
|
646 ffi->loadFor(&af, &bs);
|
Chris@45
|
647 }
|
Chris@45
|
648 }
|
Chris@45
|
649
|
Chris@46
|
650 std::cerr << "Dumping out our local store to local2.ttl" << std::endl;
|
Chris@46
|
651 bs.save("local2.ttl");
|
Chris@44
|
652
|
Chris@44
|
653 /*
|
Chris@44
|
654 BasicStore *index = new BasicStore;
|
Chris@44
|
655
|
Chris@44
|
656 QDir features(QDir::home().filePath(featuresRelPath));
|
Chris@44
|
657 features.setFilter(QDir::Files);
|
Chris@44
|
658 for (unsigned int i = 0; i < features.count(); ++i) {
|
Chris@44
|
659 QFileInfo fi(features.filePath(features[i]));
|
Chris@44
|
660 if (fi.isFile() && fi.isReadable()) {
|
Chris@44
|
661 std::cout << fi.fileName().toStdString() << std::endl;
|
Chris@44
|
662 try {
|
Chris@44
|
663 QUrl fileUrl(QUrl::fromLocalFile(fi.filePath()));
|
Chris@44
|
664 BasicStore *b = BasicStore::load(fileUrl);
|
Chris@44
|
665 Triples ts = b->match
|
Chris@44
|
666 (Triple(Node(), "a",
|
Chris@44
|
667 Uri("http://purl.org/ontology/mo/AudioFile")));
|
Chris@44
|
668 foreach (Triple t, ts) {
|
Chris@44
|
669 std::cout << "Audio file: <" << t.a.value.toStdString() << ">" << std::endl;
|
Chris@44
|
670 index->add(Triple(Uri(fileUrl), "a",
|
Chris@44
|
671 Uri("http://xmlns.com/foaf/0.1/Document")));
|
Chris@44
|
672 index->add(Triple(Uri(fileUrl),
|
Chris@44
|
673 Uri("http://xmlns.com/foaf/0.1/primaryTopic"),
|
Chris@44
|
674 t.a));
|
Chris@44
|
675 }
|
Chris@44
|
676 } catch (...) { }
|
Chris@44
|
677 }
|
Chris@44
|
678 }
|
Chris@44
|
679
|
Chris@44
|
680
|
Chris@44
|
681
|
Chris@44
|
682 index->save("index.ttl");
|
Chris@44
|
683 */
|
Chris@44
|
684 /*
|
Chris@32
|
685 if (argc < 3) usage(argv[0]);
|
Chris@32
|
686 QString inRDFFileName = argv[1];
|
Chris@32
|
687 QString command = argv[2];
|
Chris@32
|
688 QStringList args;
|
Chris@32
|
689 for (int i = 3; i < argc; ++i) {
|
Chris@32
|
690 args.push_back(argv[i]);
|
Chris@32
|
691 }
|
Chris@32
|
692
|
Chris@34
|
693 //!!! unit test!
|
Chris@34
|
694 int c = Work::compareCatalogueNumberTexts("Op. 1 no 4", "Op. 3 no 2");
|
Chris@34
|
695 std::cerr << c << std::endl;
|
Chris@34
|
696 c = Work::compareCatalogueNumberTexts("1 no 4", "3 no 2");
|
Chris@34
|
697 std::cerr << c << std::endl;
|
Chris@34
|
698 c = Work::compareCatalogueNumberTexts("4 no 2", "3 no 2");
|
Chris@34
|
699 std::cerr << c << std::endl;
|
Chris@34
|
700 c = Work::compareCatalogueNumberTexts("Opus 4 no 2", "3 no 2");
|
Chris@34
|
701 std::cerr << c << std::endl;
|
Chris@34
|
702 c = Work::compareCatalogueNumberTexts("Op 141", "K. 21");
|
Chris@34
|
703 std::cerr << c << std::endl;
|
Chris@34
|
704 c = Work::compareCatalogueNumberTexts("Op 14", "K. 21");
|
Chris@34
|
705 std::cerr << c << std::endl;
|
Chris@34
|
706 c = Work::compareCatalogueNumberTexts("Op 6a", "Op 6");
|
Chris@34
|
707 std::cerr << c << std::endl;
|
Chris@34
|
708 c = Work::compareCatalogueNumberTexts("Op 6a", "Op 6b");
|
Chris@34
|
709 std::cerr << c << std::endl;
|
Chris@34
|
710 c = Work::compareCatalogueNumberTexts("Op 6a", "Op 7");
|
Chris@34
|
711 std::cerr << c << std::endl;
|
Chris@34
|
712 c = Work::compareCatalogueNumberTexts("Hob XXIIId:Es1", "Hob XXII:B04");
|
Chris@34
|
713 std::cerr << c << std::endl;
|
Chris@34
|
714
|
Chris@32
|
715 BasicStore *store = new BasicStore();
|
Chris@32
|
716 store->setBaseUri(Uri("http://dbtune.org/classical/resource/"));
|
Chris@32
|
717 ObjectLoader *loader = new ObjectLoader(store);
|
Chris@32
|
718
|
Chris@32
|
719 TypeMapping tm;
|
Chris@32
|
720
|
Chris@32
|
721 TypeRegistrar::registerTypes();
|
Chris@32
|
722 TypeRegistrar::addMappings(store, &tm);
|
Chris@32
|
723
|
Chris@32
|
724 loader->setTypeMapping(tm);
|
Chris@32
|
725
|
Chris@32
|
726 if (!load(store, inRDFFileName)) {
|
Chris@32
|
727 cerr << "Failed to load data source" << endl;
|
Chris@32
|
728 return 1;
|
Chris@32
|
729 }
|
Chris@32
|
730
|
Chris@32
|
731 cerr << "Imported RDF data, mapping to objects...";
|
Chris@32
|
732 QObjectList objects = loader->loadAll();
|
Chris@32
|
733 cerr << " done" << endl;
|
Chris@32
|
734
|
Chris@32
|
735 delete loader;
|
Chris@32
|
736
|
Chris@32
|
737 foreach (QObject *o, objects) {
|
Chris@32
|
738 Composer *c = qobject_cast<Composer *>(o);
|
Chris@33
|
739 if (c) {
|
Chris@33
|
740 allComposers.push_back(c);
|
Chris@33
|
741 composerAliases.insert(c->name(), c);
|
Chris@33
|
742 foreach (QString alias, c->aliases()) {
|
Chris@33
|
743 composerAliases.insert(alias, c);
|
Chris@33
|
744 }
|
Chris@33
|
745 composerUris.insert(c->property("uri").value<Uri>(), c);
|
Chris@33
|
746 foreach (Uri otherUri, c->otherURIs()) {
|
Chris@33
|
747 composerUris.insert(otherUri, c);
|
Chris@33
|
748 }
|
Chris@33
|
749 }
|
Chris@32
|
750 }
|
Chris@32
|
751
|
Chris@32
|
752 QList<Work *> works;
|
Chris@32
|
753 foreach (QObject *o, objects) {
|
Chris@32
|
754 Work *w = qobject_cast<Work *>(o);
|
Chris@32
|
755 if (w) works.push_back(w);
|
Chris@32
|
756 }
|
Chris@32
|
757
|
Chris@32
|
758 foreach (Work *w, works) {
|
Chris@34
|
759 allWorks.push_back(w);
|
Chris@32
|
760 Composition *c = w->composition();
|
Chris@32
|
761 if (c) {
|
Chris@32
|
762 Composer *cp = c->composer();
|
Chris@37
|
763 if (cp) worksMap[cp].push_back(w);
|
Chris@32
|
764 }
|
Chris@32
|
765 }
|
Chris@32
|
766
|
Chris@43
|
767 BasicStore localStore;
|
Chris@43
|
768 TypeRegistrar::addMappings(&localStore, &tm);
|
Chris@43
|
769
|
Chris@43
|
770 ObjectStorer *localStorer = new ObjectStorer(&localStore);
|
Chris@43
|
771 localStorer->setTypeMapping(tm);
|
Chris@43
|
772 // localStorer->setFollowPolicy(ObjectStorer::FollowObjectProperties);
|
Chris@43
|
773
|
Chris@32
|
774 if (command == "guess") {
|
Chris@32
|
775 if (args.empty()) usage(argv[0]);
|
Chris@32
|
776 foreach (QString track, args) {
|
Chris@45
|
777 Signal *tf = guess(track);
|
Chris@43
|
778 localStorer->store(tf);
|
Chris@32
|
779 }
|
Chris@32
|
780 }
|
Chris@43
|
781
|
Chris@43
|
782 delete localStorer;
|
Chris@43
|
783 localStore.save("local.ttl");
|
Chris@32
|
784
|
Chris@32
|
785 delete store;
|
Chris@44
|
786 */
|
Chris@44
|
787
|
Chris@33
|
788 TempDirectory::getInstance()->cleanup();
|
Chris@32
|
789 }
|
Chris@32
|
790
|