File indexing completed on 2024-05-05 04:56:11

0001 /*
0002     SPDX-FileCopyrightText: 2005-2007 Richard Lärkäng <nouseforaname@home.se>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "musicbrainzlookup.h"
0008 
0009 #include "kcddbi18n.h"
0010 
0011 #include <musicbrainz5/Query.h>
0012 #include <musicbrainz5/Medium.h>
0013 #include <musicbrainz5/Release.h>
0014 #include <musicbrainz5/ReleaseGroup.h>
0015 #include <musicbrainz5/Track.h>
0016 #include <musicbrainz5/Recording.h>
0017 #include <musicbrainz5/Disc.h>
0018 #include <musicbrainz5/HTTPFetch.h>
0019 #include <musicbrainz5/ArtistCredit.h>
0020 #include <musicbrainz5/Artist.h>
0021 #include <musicbrainz5/NameCredit.h>
0022 #include <musicbrainz5/SecondaryType.h>
0023 
0024 #include <QCryptographicHash>
0025 #include <QDebug>
0026 #include <QRegExp>
0027 
0028 #include <cstdio>
0029 #include <cstring>
0030 
0031 namespace KCDDB
0032 {
0033   MusicBrainzLookup::MusicBrainzLookup()
0034   {
0035 
0036   }
0037 
0038   MusicBrainzLookup::~MusicBrainzLookup()
0039   {
0040 
0041   }
0042 
0043   Result MusicBrainzLookup::lookup( const QString &, uint, const TrackOffsetList & trackOffsetList )
0044   {
0045     QString discId = calculateDiscId(trackOffsetList);
0046 
0047     qDebug() << "Should lookup " << discId;
0048 
0049     MusicBrainz5::CQuery Query("libkcddb-0.5");
0050 
0051     // Code adapted from libmusicbrainz/examples/cdlookup.cc
0052 
0053     try {
0054       MusicBrainz5::CMetadata Metadata=Query.Query("discid",discId.toLatin1().constData());
0055 
0056       if (Metadata.Disc() && Metadata.Disc()->ReleaseList())
0057       {
0058         MusicBrainz5::CReleaseList *ReleaseList=Metadata.Disc()->ReleaseList();
0059         qDebug() << "Found " << ReleaseList->NumItems() << " release(s)";
0060 
0061         int relnr=1;
0062 
0063         for (int i = 0; i < ReleaseList->NumItems(); i++)
0064         {
0065           MusicBrainz5::CRelease* Release=ReleaseList->Item(i);
0066 
0067           //The releases returned from LookupDiscID don't contain full information
0068 
0069           MusicBrainz5::CQuery::tParamMap Params;
0070           Params["inc"]="artists labels recordings release-groups url-rels discids artist-credits";
0071 
0072           std::string ReleaseID=Release->ID();
0073 
0074           MusicBrainz5::CMetadata Metadata2=Query.Query("release",ReleaseID,"",Params);
0075           if (Metadata2.Release())
0076           {
0077             MusicBrainz5::CRelease *FullRelease=Metadata2.Release();
0078 
0079             //However, these releases will include information for all media in the release
0080             //So we need to filter out the only the media we want.
0081 
0082             MusicBrainz5::CMediumList MediaList=FullRelease->MediaMatchingDiscID(discId.toLatin1().constData());
0083 
0084             if (MediaList.NumItems() > 0)
0085             {
0086               /*if (FullRelease->ReleaseGroup())
0087                 qDebug() << "Release group title: " << FullRelease->ReleaseGroup()->Title();
0088               else
0089                 qDebug() << "No release group for this release";*/
0090 
0091               qDebug() << "Found " << MediaList.NumItems() << " media item(s)";
0092 
0093               for (int i=0; i < MediaList.NumItems(); i++)
0094               {
0095                 MusicBrainz5::CMedium* Medium= MediaList.Item(i);
0096 
0097                 /*qDebug() << "Found media: '" << Medium.Title() << "', position " << Medium.Position();*/
0098 
0099                 CDInfo info;
0100                 info.set(QLatin1String( "source" ), QLatin1String( "musicbrainz" ));
0101                 // Uses musicbrainz discid for the first release,
0102                 // then discid-2, discid-3 and so on, to
0103                 // allow multiple releases with the same discid
0104                 if (relnr == 1)
0105                   info.set(QLatin1String( "discid" ), discId);
0106                 else
0107                   info.set(QLatin1String( "discid" ), QVariant(discId+QLatin1String( "-" )+QString::number(relnr)));
0108 
0109                 QString title = QString::fromUtf8(FullRelease->Title().c_str());
0110 
0111                 if (FullRelease->MediumList()->NumItems() > 1)
0112                   title = i18n("%1 (disc %2)", title, Medium->Position());
0113 
0114                 info.set(Title, title);
0115                 info.set(Artist, artistFromCreditList(FullRelease->ArtistCredit()));
0116 
0117                 QString date = QString::fromUtf8(FullRelease->Date().c_str());
0118                 QRegExp yearRe(QString::fromUtf8("^(\\d{4,4})(-\\d{1,2}-\\d{1,2})?$"));
0119                 int year = 0;
0120                 if (yearRe.indexIn(date) > -1)
0121                 {
0122                   QString yearString = yearRe.cap(1);
0123                   bool ok;
0124                   year=yearString.toInt(&ok);
0125                   if (!ok)
0126                     year = 0;
0127                 }
0128                 info.set(Year, year);
0129 
0130                 MusicBrainz5::CTrackList *TrackList=Medium->TrackList();
0131                 if (TrackList)
0132                 {
0133                   for (int i=0; i < TrackList->NumItems(); i++)
0134                   {
0135                     MusicBrainz5::CTrack* Track=TrackList->Item(i);
0136                     MusicBrainz5::CRecording *Recording=Track->Recording();
0137 
0138                     TrackInfo& track = info.track(i);
0139 
0140                     // Prefer title and artist from the track credits, but
0141                     // it appears to be empty if same as in Recording
0142                     // Noticeable in the musicbrainztest-fulldate test,
0143                     // where the title on the credits of track 18 are
0144                     // "Bara om min älskade väntar", but the recording
0145                     // has title "Men bara om min älskade"
0146                     if(Recording && Track->ArtistCredit() == nullptr)
0147                       track.set(Artist, artistFromCreditList(Recording->ArtistCredit()));
0148                     else
0149                       track.set(Artist, artistFromCreditList(Track->ArtistCredit()));
0150 
0151                     if(Recording && Track->Title().empty())
0152                       track.set(Title, QString::fromUtf8(Recording->Title().c_str()));
0153                     else
0154                       track.set(Title, QString::fromUtf8(Track->Title().c_str()));
0155                   }
0156                 }
0157                 cdInfoList_ << info;
0158                 relnr++;
0159               }
0160             }
0161           }
0162         }
0163       }
0164     }
0165 
0166     catch (MusicBrainz5::CConnectionError& Error)
0167     {
0168       qDebug() << "Connection Exception: '" << Error.what() << "'";
0169       qDebug() << "LastResult: " << Query.LastResult();
0170       qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
0171       qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
0172 
0173       return ServerError;
0174     }
0175 
0176     catch (MusicBrainz5::CTimeoutError& Error)
0177     {
0178       qDebug() << "Timeout Exception: '" << Error.what() << "'";
0179       qDebug() << "LastResult: " << Query.LastResult();
0180       qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
0181       qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
0182 
0183       return ServerError;
0184     }
0185 
0186     catch (MusicBrainz5::CAuthenticationError& Error)
0187     {
0188       qDebug() << "Authentication Exception: '" << Error.what() << "'";
0189       qDebug() << "LastResult: " << Query.LastResult();
0190       qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
0191       qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
0192 
0193       return ServerError;
0194     }
0195 
0196     catch (MusicBrainz5::CFetchError& Error)
0197     {
0198       qDebug() << "Fetch Exception: '" << Error.what() << "'";
0199       qDebug() << "LastResult: " << Query.LastResult();
0200       qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
0201       qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
0202 
0203       return ServerError;
0204     }
0205 
0206     catch (MusicBrainz5::CRequestError& Error)
0207     {
0208       qDebug() << "Request Exception: '" << Error.what() << "'";
0209       qDebug() << "LastResult: " << Query.LastResult();
0210       qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
0211       qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
0212 
0213       return ServerError;
0214     }
0215 
0216     catch (MusicBrainz5::CResourceNotFoundError& Error)
0217     {
0218       qDebug() << "ResourceNotFound Exception: '" << Error.what() << "'";
0219       qDebug() << "LastResult: " << Query.LastResult();
0220       qDebug() << "LastHTTPCode: " << Query.LastHTTPCode();
0221       qDebug() << "LastErrorMessage: " << QString::fromUtf8(Query.LastErrorMessage().c_str());
0222 
0223       return ServerError;
0224     }
0225 
0226     if (cdInfoList_.isEmpty())
0227     {
0228         qDebug() << "No record found";
0229         return NoRecordFound;
0230     }
0231 
0232     qDebug() << "Query succeeded :-)";
0233 
0234     return Success;
0235   }
0236 
0237   QString MusicBrainzLookup::calculateDiscId(const TrackOffsetList & trackOffsetList )
0238   {
0239     // Code based on libmusicbrainz/lib/diskid.cpp
0240 
0241     int numTracks = trackOffsetList.count()-1;
0242 
0243     QCryptographicHash sha(QCryptographicHash::Sha1);
0244     char           temp[9];
0245 
0246     // FIXME How do I check that?
0247     int firstTrack = 1;
0248     int lastTrack = numTracks;
0249 
0250     sprintf(temp, "%02X", firstTrack);
0251 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0252     sha.addData(QByteArrayView(temp, strlen(temp)));
0253 #else
0254     sha.addData(temp, strlen(temp));
0255 #endif
0256 
0257     sprintf(temp, "%02X", lastTrack);
0258 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0259     sha.addData(QByteArrayView(temp, strlen(temp)));
0260 #else
0261     sha.addData(temp, strlen(temp));
0262 #endif
0263 
0264     for(int i = 0; i < 100; i++)
0265     {
0266       long offset;
0267       if (i == 0)
0268         offset = trackOffsetList[numTracks];
0269       else if (i <= numTracks)
0270         offset = trackOffsetList[i-1];
0271       else
0272         offset = 0;
0273 
0274       sprintf(temp, "%08lX", offset);
0275 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0276       sha.addData(QByteArrayView(temp, strlen(temp)));
0277 #else
0278       sha.addData(temp, strlen(temp));
0279 #endif
0280     }
0281 
0282     QByteArray base64 = sha.result().toBase64();
0283 
0284     // '/' '+' and '=' replaced for MusicBrainz
0285     QString res = QString::fromLatin1(base64).replace(QLatin1Char( '/' ),QLatin1String( "_" )).replace(QLatin1Char( '+' ),QLatin1String( "." )).replace(QLatin1Char( '=' ),QLatin1String( "-" ));
0286 
0287     return res;
0288   }
0289 
0290   CDInfoList MusicBrainzLookup::cacheFiles(const TrackOffsetList &offsetList, const Config& c )
0291   {
0292     CDInfoList infoList;
0293     QStringList cddbCacheDirs = c.cacheLocations();
0294     QString discid = calculateDiscId(offsetList);
0295 
0296     for (QStringList::const_iterator cddbCacheDir = cddbCacheDirs.constBegin();
0297         cddbCacheDir != cddbCacheDirs.constEnd(); ++cddbCacheDir)
0298     {
0299       // Looks for all files in cddbdir/musicbrainz/discid*
0300       // Several files can correspond to the same discid,
0301       // then they are named discid, discid-2, discid-3 and so on
0302       QDir dir(*cddbCacheDir+QLatin1String( "/musicbrainz/" ));
0303       dir.setNameFilters(QStringList(discid+QLatin1String( "*" )));
0304 
0305       QStringList files = dir.entryList();
0306       qDebug() << "Cache files found: " << files.count();
0307       for (QStringList::iterator it = files.begin(); it != files.end(); ++it)
0308       {
0309         QFile f( dir.filePath(*it) );
0310         if ( f.exists() && f.open(QIODevice::ReadOnly) )
0311         {
0312           QTextStream ts(&f);
0313 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0314           ts.setCodec("UTF-8");
0315 #endif
0316           QString cddbData = ts.readAll();
0317           f.close();
0318           CDInfo info;
0319           info.load(cddbData);
0320           info.set(QLatin1String( "source" ), QLatin1String( "musicbrainz" ));
0321           info.set(QLatin1String( "discid" ), discid);
0322 
0323           infoList.append( info );
0324         }
0325         else
0326           qDebug() << "Could not read file: " << f.fileName();
0327       }
0328     }
0329 
0330     return infoList;
0331   }
0332 
0333   QString MusicBrainzLookup::artistFromCreditList(MusicBrainz5::CArtistCredit * artistCredit )
0334   {
0335     qDebug()/* << k_funcinfo*/;
0336     QString artistName;
0337 
0338     MusicBrainz5::CNameCreditList *ArtistList=artistCredit->NameCreditList();
0339 
0340     if (ArtistList)
0341     {
0342       for (int i=0; i < ArtistList->NumItems(); i++)
0343       {
0344         MusicBrainz5::CNameCredit* Name=ArtistList->Item(i);
0345         MusicBrainz5::CArtist* Artist = Name->Artist();
0346 
0347         if (!Name->Name().empty())
0348           artistName += QString::fromUtf8(Name->Name().c_str());
0349         else
0350           artistName += QString::fromUtf8(Artist->Name().c_str());
0351 
0352         artistName += QString::fromUtf8(Name->JoinPhrase().c_str());
0353       }
0354 
0355       qDebug() << "Artist:" << artistName;
0356 
0357     }
0358 
0359     return artistName;
0360   }
0361 }
0362 
0363 #include "moc_musicbrainzlookup.cpp"
0364 
0365 // vim:tabstop=2:shiftwidth=2:expandtab:cinoptions=(s,U1,m1