KCDDB

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

KDE's Doxygen guidelines are available online.