KCDDB

musicbrainzlookup.cpp
1/*
2 SPDX-FileCopyrightText: 2005-2007 Richard Lärkäng <nouseforaname@home.se>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "musicbrainzlookup.h"
8
9#include "kcddbi18n.h"
10
11#include <musicbrainz5/Query.h>
12#include <musicbrainz5/Medium.h>
13#include <musicbrainz5/Release.h>
14#include <musicbrainz5/ReleaseGroup.h>
15#include <musicbrainz5/Track.h>
16#include <musicbrainz5/Recording.h>
17#include <musicbrainz5/Disc.h>
18#include <musicbrainz5/HTTPFetch.h>
19#include <musicbrainz5/ArtistCredit.h>
20#include <musicbrainz5/Artist.h>
21#include <musicbrainz5/NameCredit.h>
22#include <musicbrainz5/SecondaryType.h>
23
24#include <QCryptographicHash>
25#include <QDebug>
26#include <QRegularExpression>
27
28#include <cstdio>
29#include <cstring>
30
31namespace KCDDB
32{
33 MusicBrainzLookup::MusicBrainzLookup()
34 {
35
36 }
37
38 MusicBrainzLookup::~MusicBrainzLookup()
39 {
40
41 }
42
43 Result MusicBrainzLookup::lookup( const QString &, uint, const TrackOffsetList & trackOffsetList )
44 {
45 QString discId = calculateDiscId(trackOffsetList);
46
47 qDebug() << "Should lookup " << discId;
48
49 MusicBrainz5::CQuery Query("libkcddb-0.5");
50
51 // Code adapted from libmusicbrainz/examples/cdlookup.cc
52
53 try {
54 MusicBrainz5::CMetadata Metadata=Query.Query("discid",discId.toLatin1().constData());
55
56 if (Metadata.Disc() && Metadata.Disc()->ReleaseList())
57 {
58 MusicBrainz5::CReleaseList *ReleaseList=Metadata.Disc()->ReleaseList();
59 qDebug() << "Found " << ReleaseList->NumItems() << " release(s)";
60
61 int relnr=1;
62
63 for (int i = 0; i < ReleaseList->NumItems(); i++)
64 {
65 MusicBrainz5::CRelease* Release=ReleaseList->Item(i);
66
67 //The releases returned from LookupDiscID don't contain full information
68
69 MusicBrainz5::CQuery::tParamMap Params;
70 Params["inc"]="artists labels recordings release-groups url-rels discids artist-credits";
71
72 std::string ReleaseID=Release->ID();
73
74 MusicBrainz5::CMetadata Metadata2=Query.Query("release",ReleaseID,"",Params);
75 if (Metadata2.Release())
76 {
77 MusicBrainz5::CRelease *FullRelease=Metadata2.Release();
78
79 //However, these releases will include information for all media in the release
80 //So we need to filter out the only the media we want.
81
82 MusicBrainz5::CMediumList MediaList=FullRelease->MediaMatchingDiscID(discId.toLatin1().constData());
83
84 if (MediaList.NumItems() > 0)
85 {
86 /*if (FullRelease->ReleaseGroup())
87 qDebug() << "Release group title: " << FullRelease->ReleaseGroup()->Title();
88 else
89 qDebug() << "No release group for this release";*/
90
91 qDebug() << "Found " << MediaList.NumItems() << " media item(s)";
92
93 for (int i=0; i < MediaList.NumItems(); i++)
94 {
95 MusicBrainz5::CMedium* Medium= MediaList.Item(i);
96
97 /*qDebug() << "Found media: '" << Medium.Title() << "', position " << Medium.Position();*/
98
99 CDInfo info;
100 info.set(QLatin1String( "source" ), QLatin1String( "musicbrainz" ));
101 // Uses musicbrainz discid for the first release,
102 // then discid-2, discid-3 and so on, to
103 // allow multiple releases with the same discid
104 if (relnr == 1)
105 info.set(QLatin1String( "discid" ), discId);
106 else
107 info.set(QLatin1String( "discid" ), QVariant(discId+QLatin1String( "-" )+QString::number(relnr)));
108
109 QString title = QString::fromUtf8(FullRelease->Title().c_str());
110
111 if (FullRelease->MediumList()->NumItems() > 1)
112 title = i18n("%1 (disc %2)", title, Medium->Position());
113
114 info.set(Title, title);
115 info.set(Artist, artistFromCreditList(FullRelease->ArtistCredit()));
116
117 QString date = QString::fromUtf8(FullRelease->Date().c_str());
118 const QRegularExpression yearRe(QString::fromUtf8("^(\\d{4,4})(-\\d{1,2}-\\d{1,2})?$"));
119 int year = 0;
120 if (const auto yearMatch = yearRe.match(date); yearMatch.hasMatch())
121 {
122 QString yearString = yearMatch.captured(1);
123 bool ok;
124 year=yearString.toInt(&ok);
125 if (!ok)
126 year = 0;
127 }
128 info.set(Year, year);
129
130 MusicBrainz5::CTrackList *TrackList=Medium->TrackList();
131 if (TrackList)
132 {
133 for (int i=0; i < TrackList->NumItems(); i++)
134 {
135 MusicBrainz5::CTrack* Track=TrackList->Item(i);
136 MusicBrainz5::CRecording *Recording=Track->Recording();
137
138 TrackInfo& track = info.track(i);
139
140 // Prefer title and artist from the track credits, but
141 // it appears to be empty if same as in Recording
142 // Noticeable in the musicbrainztest-fulldate test,
143 // where the title on the credits of track 18 are
144 // "Bara om min älskade väntar", but the recording
145 // has title "Men bara om min älskade"
146 if(Recording && Track->ArtistCredit() == nullptr)
147 track.set(Artist, artistFromCreditList(Recording->ArtistCredit()));
148 else
149 track.set(Artist, artistFromCreditList(Track->ArtistCredit()));
150
151 if(Recording && Track->Title().empty())
152 track.set(Title, QString::fromUtf8(Recording->Title().c_str()));
153 else
154 track.set(Title, QString::fromUtf8(Track->Title().c_str()));
155 }
156 }
157 cdInfoList_ << info;
158 relnr++;
159 }
160 }
161 }
162 }
163 }
164 }
165
166 catch (MusicBrainz5::CConnectionError& Error)
167 {
168 qDebug() << "Connection Exception: '" << Error.what() << "'";
169 qDebug() << "LastResult: " << Query.LastResult();
170 qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
171 qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
172
173 return ServerError;
174 }
175
176 catch (MusicBrainz5::CTimeoutError& Error)
177 {
178 qDebug() << "Timeout Exception: '" << Error.what() << "'";
179 qDebug() << "LastResult: " << Query.LastResult();
180 qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
181 qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
182
183 return ServerError;
184 }
185
186 catch (MusicBrainz5::CAuthenticationError& Error)
187 {
188 qDebug() << "Authentication Exception: '" << Error.what() << "'";
189 qDebug() << "LastResult: " << Query.LastResult();
190 qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
191 qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
192
193 return ServerError;
194 }
195
196 catch (MusicBrainz5::CFetchError& Error)
197 {
198 qDebug() << "Fetch Exception: '" << Error.what() << "'";
199 qDebug() << "LastResult: " << Query.LastResult();
200 qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
201 qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
202
203 return ServerError;
204 }
205
206 catch (MusicBrainz5::CRequestError& Error)
207 {
208 qDebug() << "Request Exception: '" << Error.what() << "'";
209 qDebug() << "LastResult: " << Query.LastResult();
210 qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
211 qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
212
213 return ServerError;
214 }
215
216 catch (MusicBrainz5::CResourceNotFoundError& Error)
217 {
218 qDebug() << "ResourceNotFound Exception: '" << Error.what() << "'";
219 qDebug() << "LastResult: " << Query.LastResult();
220 qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
221 qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
222
223 return ServerError;
224 }
225
226 if (cdInfoList_.isEmpty())
227 {
228 qDebug() << "No record found";
229 return NoRecordFound;
230 }
231
232 qDebug() << "Query succeeded :-)";
233
234 return Success;
235 }
236
237 QString MusicBrainzLookup::calculateDiscId(const TrackOffsetList & trackOffsetList )
238 {
239 // Code based on libmusicbrainz/lib/diskid.cpp
240
241 int numTracks = trackOffsetList.count()-1;
242
244 char temp[9];
245
246 // FIXME How do I check that?
247 int firstTrack = 1;
248 int lastTrack = numTracks;
249
250 sprintf(temp, "%02X", firstTrack);
251#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
252 sha.addData(QByteArrayView(temp, strlen(temp)));
253#else
254 sha.addData(temp, strlen(temp));
255#endif
256
257 sprintf(temp, "%02X", lastTrack);
258#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
259 sha.addData(QByteArrayView(temp, strlen(temp)));
260#else
261 sha.addData(temp, strlen(temp));
262#endif
263
264 for(int i = 0; i < 100; i++)
265 {
266 long offset;
267 if (i == 0)
268 offset = trackOffsetList[numTracks];
269 else if (i <= numTracks)
270 offset = trackOffsetList[i-1];
271 else
272 offset = 0;
273
274 sprintf(temp, "%08lX", offset);
275#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
276 sha.addData(QByteArrayView(temp, strlen(temp)));
277#else
278 sha.addData(temp, strlen(temp));
279#endif
280 }
281
282 QByteArray base64 = sha.result().toBase64();
283
284 // '/' '+' and '=' replaced for MusicBrainz
286
287 return res;
288 }
289
290 CDInfoList MusicBrainzLookup::cacheFiles(const TrackOffsetList &offsetList, const Config& c )
291 {
292 CDInfoList infoList;
293 QStringList cddbCacheDirs = c.cacheLocations();
294 QString discid = calculateDiscId(offsetList);
295
296 for (QStringList::const_iterator cddbCacheDir = cddbCacheDirs.constBegin();
297 cddbCacheDir != cddbCacheDirs.constEnd(); ++cddbCacheDir)
298 {
299 // Looks for all files in cddbdir/musicbrainz/discid*
300 // Several files can correspond to the same discid,
301 // then they are named discid, discid-2, discid-3 and so on
302 QDir dir(*cddbCacheDir+QLatin1String( "/musicbrainz/" ));
303 dir.setNameFilters(QStringList(discid+QLatin1String( "*" )));
304
305 QStringList files = dir.entryList();
306 qDebug() << "Cache files found: " << files.count();
307 for (QStringList::iterator it = files.begin(); it != files.end(); ++it)
308 {
309 QFile f( dir.filePath(*it) );
310 if ( f.exists() && f.open(QIODevice::ReadOnly) )
311 {
312 QTextStream ts(&f);
313#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
314 ts.setCodec("UTF-8");
315#endif
316 QString cddbData = ts.readAll();
317 f.close();
318 CDInfo info;
319 info.load(cddbData);
320 info.set(QLatin1String( "source" ), QLatin1String( "musicbrainz" ));
321 info.set(QLatin1String( "discid" ), discid);
322
323 infoList.append( info );
324 }
325 else
326 qDebug() << "Could not read file: " << f.fileName();
327 }
328 }
329
330 return infoList;
331 }
332
333 QString MusicBrainzLookup::artistFromCreditList(MusicBrainz5::CArtistCredit * artistCredit )
334 {
335 qDebug()/* << k_funcinfo*/;
336 QString artistName;
337
338 MusicBrainz5::CNameCreditList *ArtistList=artistCredit->NameCreditList();
339
340 if (ArtistList)
341 {
342 for (int i=0; i < ArtistList->NumItems(); i++)
343 {
344 MusicBrainz5::CNameCredit* Name=ArtistList->Item(i);
345 MusicBrainz5::CArtist* Artist = Name->Artist();
346
347 if (!Name->Name().empty())
348 artistName += QString::fromUtf8(Name->Name().c_str());
349 else
350 artistName += QString::fromUtf8(Artist->Name().c_str());
351
352 artistName += QString::fromUtf8(Name->JoinPhrase().c_str());
353 }
354
355 qDebug() << "Artist:" << artistName;
356
357 }
358
359 return artistName;
360 }
361}
362
363#include "moc_musicbrainzlookup.cpp"
364
365// vim:tabstop=2:shiftwidth=2:expandtab:cinoptions=(s,U1,m1
QString i18n(const char *text, const TYPE &arg...)
KIOCORE_EXPORT QString dir(const QString &fileClass)
const char * constData() const const
QByteArray toBase64(Base64Options options) const const
iterator begin()
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype count() const const
iterator end()
bool isEmpty() const const
qsizetype count() const const
QString & append(QChar ch)
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
int toInt(bool *ok, int base) const const
QByteArray toLatin1() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:58 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.