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