File indexing completed on 2024-05-19 12:37:00
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