File indexing completed on 2024-05-19 04:56:30
0001 /** 0002 * \file musicbrainzimporter.cpp 0003 * MusicBrainz release database importer. 0004 * 0005 * \b Project: Kid3 0006 * \author Urs Fleisch 0007 * \date 13 Oct 2006 0008 * 0009 * Copyright (C) 2006-2024 Urs Fleisch 0010 * 0011 * This file is part of Kid3. 0012 * 0013 * Kid3 is free software; you can redistribute it and/or modify 0014 * it under the terms of the GNU General Public License as published by 0015 * the Free Software Foundation; either version 2 of the License, or 0016 * (at your option) any later version. 0017 * 0018 * Kid3 is distributed in the hope that it will be useful, 0019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0021 * GNU General Public License for more details. 0022 * 0023 * You should have received a copy of the GNU General Public License 0024 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0025 */ 0026 0027 #include "musicbrainzimporter.h" 0028 #include <QDomDocument> 0029 #include <QUrl> 0030 #include <QRegularExpression> 0031 #include "serverimporterconfig.h" 0032 #include "trackdatamodel.h" 0033 #include "musicbrainzconfig.h" 0034 #include "genres.h" 0035 0036 /** 0037 * Constructor. 0038 * 0039 * @param netMgr network access manager 0040 * @param trackDataModel track data to be filled with imported values 0041 */ 0042 MusicBrainzImporter::MusicBrainzImporter( 0043 QNetworkAccessManager* netMgr, TrackDataModel *trackDataModel) 0044 : ServerImporter(netMgr, trackDataModel) 0045 { 0046 setObjectName(QLatin1String("MusicBrainzImporter")); 0047 m_headers["User-Agent"] = "curl/7.52.1"; 0048 } 0049 0050 /** 0051 * Name of import source. 0052 * @return name. 0053 */ 0054 const char* MusicBrainzImporter::name() const { 0055 return QT_TRANSLATE_NOOP("@default", "MusicBrainz Release"); 0056 } 0057 0058 /** NULL-terminated array of server strings, 0 if not used */ 0059 const char** MusicBrainzImporter::serverList() const 0060 { 0061 return nullptr; 0062 } 0063 0064 /** default server, 0 to disable */ 0065 const char* MusicBrainzImporter::defaultServer() const { 0066 return nullptr; 0067 } 0068 0069 /** anchor to online help, 0 to disable */ 0070 const char* MusicBrainzImporter::helpAnchor() const { 0071 return "import-musicbrainzrelease"; 0072 } 0073 0074 /** configuration, 0 if not used */ 0075 ServerImporterConfig* MusicBrainzImporter::config() const { 0076 return &MusicBrainzConfig::instance(); 0077 } 0078 0079 /** additional tags option, false if not used */ 0080 bool MusicBrainzImporter::additionalTags() const { return true; } 0081 0082 /** 0083 * Process finished findCddbAlbum request. 0084 * 0085 * @param searchStr search data received 0086 */ 0087 void MusicBrainzImporter::parseFindResults(const QByteArray& searchStr) 0088 { 0089 /* simplified XML result: 0090 <metadata> 0091 <release-list offset="0" count="3"> 0092 <release ext:score="100" id="978c7ed1-a854-4ef2-bd4e-e7c1317be854"> 0093 <title>Odin</title> 0094 <artist-credit> 0095 <name-credit> 0096 <artist id="d1075cad-33e3-496b-91b0-d4670aabf4f8"> 0097 <name>Wizard</name> 0098 <sort-name>Wizard</sort-name> 0099 </artist> 0100 </name-credit> 0101 </artist-credit> 0102 </release> 0103 */ 0104 int start = searchStr.indexOf("<?xml"); 0105 int end = searchStr.indexOf("</metadata>"); 0106 QByteArray xmlStr = searchStr; 0107 if (start >= 0 && end > start) { 0108 xmlStr = xmlStr.mid(start, end + 11 - start); 0109 } 0110 if (QDomDocument doc; doc.setContent(xmlStr, false)) { 0111 m_albumListModel->clear(); 0112 QDomElement releaseList = 0113 doc.namedItem(QLatin1String("metadata")).toElement() 0114 .namedItem(QLatin1String("release-list")).toElement(); 0115 for (QDomNode releaseNode = releaseList.namedItem(QLatin1String("release")); 0116 !releaseNode.isNull(); 0117 releaseNode = releaseNode.nextSibling()) { 0118 QDomElement release = releaseNode.toElement(); 0119 QString id = release.attribute(QLatin1String("id")); 0120 QString title = release.namedItem(QLatin1String("title")).toElement() 0121 .text(); 0122 QDomElement artist = release.namedItem(QLatin1String("artist-credit")) 0123 .toElement().namedItem(QLatin1String("name-credit")).toElement() 0124 .namedItem(QLatin1String("artist")).toElement(); 0125 QString name = artist.namedItem(QLatin1String("name")).toElement().text(); 0126 m_albumListModel->appendItem( 0127 name + QLatin1String(" - ") + title, 0128 QLatin1String("release"), 0129 id); 0130 } 0131 } 0132 } 0133 0134 namespace { 0135 0136 /** 0137 * Uppercase the first characters of each word in a string. 0138 * 0139 * @param str string with words to uppercase 0140 * 0141 * @return string with first letters in uppercase. 0142 */ 0143 QString upperCaseFirstLetters(const QString& str) 0144 { 0145 QString result(str); 0146 int len = result.length(); 0147 int pos = 0; 0148 while (pos < len) { 0149 result[pos] = result.at(pos).toUpper(); 0150 pos = result.indexOf(QLatin1Char(' '), pos); 0151 if (pos++ == -1) { 0152 break; 0153 } 0154 } 0155 return result; 0156 } 0157 0158 /** 0159 * Add involved people to a frame. 0160 * The format used is (should be converted according to tag specifications): 0161 * involvee 1 (involvement 1)\n 0162 * involvee 2 (involvement 2)\n 0163 * ... 0164 * involvee n (involvement n) 0165 * 0166 * @param frames frame collection 0167 * @param type type of frame 0168 * @param involvement involvement (e.g. instrument) 0169 * @param involvee name of involvee (e.g. musician) 0170 */ 0171 void addInvolvedPeople( 0172 FrameCollection& frames, Frame::Type type, 0173 const QString& involvement, const QString& involvee) 0174 { 0175 QString value = frames.getValue(type); 0176 if (!value.isEmpty()) value += Frame::stringListSeparator(); 0177 value += Frame::joinStringList({upperCaseFirstLetters(involvement), involvee}); 0178 frames.setValue(type, value); 0179 } 0180 0181 /** 0182 * Set tags from an XML node with a relation list. 0183 * 0184 * @param relationList relation-list with target-type Artist 0185 * @param frames tags will be added to these frames 0186 * 0187 * @return true if credits found. 0188 */ 0189 bool parseCredits(const QDomElement& relationList, FrameCollection& frames) 0190 { 0191 bool result = false; 0192 QDomNode relation(relationList.firstChild()); 0193 while (!relation.isNull()) { 0194 if (QString artist(relation.toElement().namedItem(QLatin1String("artist")) 0195 .toElement().namedItem(QLatin1String("name")) 0196 .toElement().text()); 0197 !artist.isEmpty()) { 0198 if (QString type(relation.toElement().attribute(QLatin1String("type"))); 0199 type == QLatin1String("instrument")) { 0200 if (QDomNode attributeList(relation.toElement() 0201 .namedItem(QLatin1String("attribute-list"))); 0202 !attributeList.isNull()) { 0203 addInvolvedPeople(frames, Frame::FT_Performer, 0204 attributeList.firstChild().toElement().text(), artist); 0205 } 0206 } else if (type == QLatin1String("vocal")) { 0207 addInvolvedPeople(frames, Frame::FT_Performer, type, artist); 0208 } else { 0209 static const struct { 0210 const char* credit; 0211 Frame::Type type; 0212 } creditToType[] = { 0213 { "composer", Frame::FT_Composer }, 0214 { "conductor", Frame::FT_Conductor }, 0215 { "performing orchestra", Frame::FT_AlbumArtist }, 0216 { "lyricist", Frame::FT_Lyricist }, 0217 { "publisher", Frame::FT_Publisher }, 0218 { "remixer", Frame::FT_Remixer } 0219 }; 0220 bool found = false; 0221 for (const auto& c2t : creditToType) { 0222 if (type == QString::fromLatin1(c2t.credit)) { 0223 frames.setValue(c2t.type, artist); 0224 found = true; 0225 break; 0226 } 0227 } 0228 if (!found && type != QLatin1String("tribute")) { 0229 addInvolvedPeople(frames, Frame::FT_Arranger, type, artist); 0230 } 0231 } 0232 } 0233 result = true; 0234 relation = relation.nextSibling(); 0235 } 0236 return result; 0237 } 0238 0239 /** 0240 * Transform the lower case genres returned by MusicBrainz to match the 0241 * standard genre names. 0242 * @param genre lower case genre 0243 * @return capitalized canonical genre. 0244 */ 0245 QString fixUpGenre(QString genre) 0246 { 0247 if (genre.isEmpty()) { 0248 return genre; 0249 } 0250 for (int i = 0; i < genre.length(); ++i) { 0251 if (i == 0 || genre.at(i - 1) == QLatin1Char('-') || 0252 genre.at(i - 1) == QLatin1Char(' ') || 0253 genre.at(i - 1) == QLatin1Char('&')) { 0254 genre[i] = genre[i].toUpper(); 0255 } 0256 } 0257 genre.replace(QLatin1String(" And "), QLatin1String(" & ")) 0258 .replace(QLatin1String("Ebm"), QLatin1String("EBM")) 0259 .replace(QLatin1String("Edm"), QLatin1String("EDM")) 0260 .replace(QLatin1String("Idm"), QLatin1String("IDM")) 0261 .replace(QLatin1String("Uk"), QLatin1String("UK")); 0262 return genre; 0263 } 0264 0265 /** 0266 * Get genres from an XML node with a genre-list. 0267 * @param element XML node which could have a genre-list 0268 * @return genres separated by frame string list separator, null if not found. 0269 */ 0270 QString parseGenres(const QDomElement& element) 0271 { 0272 if (QDomNode genreList = 0273 element.namedItem(QLatin1String("genre-list")); 0274 !genreList.isNull()) { 0275 QStringList genres, customGenres; 0276 for (QDomNode genreNode = genreList.namedItem(QLatin1String("genre")); 0277 !genreNode.isNull(); 0278 genreNode = genreNode.nextSibling()) { 0279 if (!genreNode.isNull()) { 0280 if (QString genre = fixUpGenre(genreNode.toElement() 0281 .namedItem(QLatin1String("name")).toElement().text()); 0282 !genre.isEmpty()) { 0283 if (int genreNum = Genres::getNumber(genre); genreNum != 255) { 0284 genres.append(QString::fromLatin1(Genres::getName(genreNum))); 0285 } else { 0286 customGenres.append(genre); 0287 } 0288 } 0289 } 0290 } 0291 genres.append(customGenres); 0292 return Frame::joinStringList(genres); 0293 } 0294 return QString(); 0295 } 0296 0297 } 0298 0299 /** 0300 * Parse result of album request and populate m_trackDataModel with results. 0301 * 0302 * @param albumStr album data received 0303 */ 0304 void MusicBrainzImporter::parseAlbumResults(const QByteArray& albumStr) 0305 { 0306 /* 0307 <metadata> 0308 <release id="978c7ed1-a854-4ef2-bd4e-e7c1317be854"> 0309 <title>Odin</title> 0310 <artist-credit> 0311 <name-credit> 0312 <artist id="d1075cad-33e3-496b-91b0-d4670aabf4f8"> 0313 <name>Wizard</name> 0314 <sort-name>Wizard</sort-name> 0315 </artist> 0316 </name-credit> 0317 </artist-credit> 0318 <date>2003-08-19</date> 0319 <asin>B00008OUEN</asin> 0320 <medium-list count="1"> 0321 <medium> 0322 <position>1</position> 0323 <track-list count="11" offset="0"> 0324 <track> 0325 <position>1</position> 0326 <recording id="dac7c002-432f-4dcb-ad57-5ebde8e258b0"> 0327 <title>The Prophecy</title> 0328 <length>319173</length> 0329 </recording> 0330 */ 0331 int start = albumStr.indexOf("<?xml"); 0332 int end = albumStr.indexOf("</metadata>"); 0333 QByteArray xmlStr = start >= 0 && end > start ? 0334 albumStr.mid(start, end + 11 - start) : albumStr; 0335 if (QDomDocument doc; doc.setContent(xmlStr, false)) { 0336 QDomElement release = 0337 doc.namedItem(QLatin1String("metadata")).toElement() 0338 .namedItem(QLatin1String("release")).toElement(); 0339 FrameCollection framesHdr; 0340 const bool standardTags = getStandardTags(); 0341 if (standardTags) { 0342 framesHdr.setAlbum(release.namedItem(QLatin1String("title")).toElement() 0343 .text()); 0344 QDomElement artist = release.namedItem(QLatin1String("artist-credit")) 0345 .toElement().namedItem(QLatin1String("name-credit")) 0346 .toElement().namedItem(QLatin1String("artist")) 0347 .toElement(); 0348 framesHdr.setArtist(artist.namedItem(QLatin1String("name")) 0349 .toElement().text()); 0350 if (QString genre = parseGenres(artist); !genre.isEmpty()) { 0351 framesHdr.setGenre(genre); 0352 } 0353 if (QString date(release.namedItem(QLatin1String("date")).toElement().text()); 0354 !date.isEmpty()) { 0355 QRegularExpression dateRe(QLatin1String(R"(^(\d{4})(?:-\d{2})?(?:-\d{2})?$)")); 0356 int year; 0357 if (auto match = dateRe.match(date); match.hasMatch()) { 0358 year = match.captured(1).toInt(); 0359 } else { 0360 year = date.toInt(); 0361 } 0362 if (year != 0) { 0363 framesHdr.setYear(year); 0364 } 0365 } 0366 } 0367 0368 ImportTrackDataVector trackDataVector(m_trackDataModel->getTrackData()); 0369 trackDataVector.setCoverArtUrl(QUrl()); 0370 const bool coverArt = getCoverArt(); 0371 if (coverArt) { 0372 if (QString asin(release.namedItem(QLatin1String("asin")).toElement().text()); 0373 !asin.isEmpty()) { 0374 trackDataVector.setCoverArtUrl( 0375 QUrl(QLatin1String("http://www.amazon.com/dp/") + asin)); 0376 } 0377 } 0378 0379 const bool additionalTags = getAdditionalTags(); 0380 if (additionalTags) { 0381 // label can be found in the label-info-list 0382 if (QDomElement labelInfoList( 0383 release.namedItem(QLatin1String("label-info-list")).toElement()); 0384 !labelInfoList.isNull()) { 0385 if (QDomElement labelInfo( 0386 labelInfoList.namedItem(QLatin1String("label-info")).toElement()); 0387 !labelInfo.isNull()) { 0388 if (QString label(labelInfo.namedItem(QLatin1String("label")) 0389 .namedItem(QLatin1String("name")) 0390 .toElement().text()); 0391 !label.isEmpty()) { 0392 framesHdr.setValue(Frame::FT_Publisher, label); 0393 } 0394 if (QString catNo(labelInfo.namedItem(QLatin1String("catalog-number")) 0395 .toElement().text()); 0396 !catNo.isEmpty()) { 0397 framesHdr.setValue(Frame::FT_CatalogNumber, catNo); 0398 } 0399 } 0400 } 0401 // Release country can be found in "country" 0402 if (QString country(release.namedItem(QLatin1String("country")) 0403 .toElement().text()); 0404 !country.isEmpty()) { 0405 framesHdr.setValue(Frame::FT_ReleaseCountry, country); 0406 } 0407 } 0408 0409 if (additionalTags || coverArt) { 0410 QDomNode relationListNode(release.firstChild()); 0411 while (!relationListNode.isNull()) { 0412 if (relationListNode.nodeName() == QLatin1String("relation-list")) { 0413 if (QDomElement relationList(relationListNode.toElement()); 0414 !relationList.isNull()) { 0415 if (QString targetType(relationList.attribute(QLatin1String("target-type"))); 0416 targetType == QLatin1String("artist")) { 0417 if (additionalTags) { 0418 parseCredits(relationList, framesHdr); 0419 } 0420 } else if (targetType == QLatin1String("url")) { 0421 if (coverArt) { 0422 QDomNode relationNode(relationList.firstChild()); 0423 while (!relationNode.isNull()) { 0424 if (relationNode.nodeName() == QLatin1String("relation")) { 0425 if (QDomElement relation(relationNode.toElement()); 0426 !relation.isNull()) { 0427 if (QString type(relation.attribute(QLatin1String("type"))); 0428 type == QLatin1String("cover art link") || 0429 type == QLatin1String("amazon asin")) { 0430 QString coverArtUrl = 0431 relation.namedItem(QLatin1String("target")) 0432 .toElement().text(); 0433 // https://www.amazon.de/gp/product/ does not work, 0434 // fix such links. 0435 coverArtUrl.replace( 0436 QRegularExpression(QLatin1String( 0437 "https://www\\.amazon\\.[^/]+/gp/product/")), 0438 QLatin1String("http://images.amazon.com/images/P/")); 0439 if (!coverArtUrl.endsWith(QLatin1String(".jpg"))) { 0440 coverArtUrl += QLatin1String(".jpg"); 0441 } 0442 trackDataVector.setCoverArtUrl( 0443 QUrl(coverArtUrl)); 0444 } 0445 } 0446 } 0447 relationNode = relationNode.nextSibling(); 0448 } 0449 } 0450 } 0451 } 0452 } 0453 relationListNode = relationListNode.nextSibling(); 0454 } 0455 } 0456 0457 auto it = trackDataVector.begin(); 0458 bool atTrackDataListEnd = it == trackDataVector.end(); 0459 int discNr = 1, trackNr = 1; 0460 bool ok; 0461 FrameCollection frames(framesHdr); 0462 QDomElement mediumList = release.namedItem(QLatin1String("medium-list")) 0463 .toElement(); 0464 int mediumCount = mediumList.attribute(QLatin1String("count")).toInt(); 0465 for (QDomNode mediumNode = mediumList.namedItem(QLatin1String("medium")); 0466 !mediumNode.isNull(); 0467 mediumNode = mediumNode.nextSibling()) { 0468 int position = mediumNode.namedItem(QLatin1String("position")) 0469 .toElement().text().toInt(&ok); 0470 if (ok) { 0471 discNr = position; 0472 } 0473 QDomElement trackList = mediumNode.namedItem(QLatin1String("track-list")) 0474 .toElement(); 0475 for (QDomNode trackNode = trackList.namedItem(QLatin1String("track")); 0476 !trackNode.isNull(); 0477 trackNode = trackNode.nextSibling()) { 0478 if (mediumCount > 1 && additionalTags) { 0479 frames.setValue(Frame::FT_Disc, QString::number(discNr)); 0480 } 0481 QDomElement track = trackNode.toElement(); 0482 position = track.namedItem(QLatin1String("position")).toElement() 0483 .text().toInt(&ok); 0484 if (ok) { 0485 trackNr = position; 0486 } 0487 if (standardTags) { 0488 frames.setTrack(trackNr); 0489 } 0490 int duration = track.namedItem(QLatin1String("length")).toElement() 0491 .text().toInt(); 0492 if (QDomElement recording = track.namedItem(QLatin1String("recording")) 0493 .toElement(); 0494 !recording.isNull()) { 0495 if (standardTags) { 0496 frames.setTitle(recording.namedItem(QLatin1String("title")) 0497 .toElement().text()); 0498 } 0499 int length = recording.namedItem(QLatin1String("length")) 0500 .toElement().text().toInt(&ok); 0501 if (ok) { 0502 duration = length; 0503 } 0504 if (QDomNode artistNode = 0505 recording.namedItem(QLatin1String("artist-credit")); 0506 !artistNode.isNull()) { 0507 QDomElement artistElement = artistNode.toElement() 0508 .namedItem(QLatin1String("name-credit")).toElement() 0509 .namedItem(QLatin1String("artist")).toElement(); 0510 if (QString artist = artistElement 0511 .namedItem(QLatin1String("name")).toElement().text(); 0512 !artist.isEmpty()) { 0513 // use the artist in the header as the album artist 0514 // and the artist in the track as the artist 0515 if (standardTags) { 0516 frames.setArtist(artist); 0517 } 0518 if (additionalTags) { 0519 frames.setValue(Frame::FT_AlbumArtist, framesHdr.getArtist()); 0520 } 0521 } 0522 if (QString genre = parseGenres(artistElement); !genre.isEmpty()) { 0523 frames.setGenre(genre); 0524 } 0525 } 0526 if (QString genre = parseGenres(recording); !genre.isEmpty()) { 0527 frames.setGenre(genre); 0528 } 0529 if (additionalTags) { 0530 QDomNode relationListNode(recording.firstChild()); 0531 while (!relationListNode.isNull()) { 0532 if (relationListNode.nodeName() == QLatin1String("relation-list")) { 0533 if (QDomElement relationList(relationListNode.toElement()); 0534 !relationList.isNull()) { 0535 if (QString targetType( 0536 relationList.attribute(QLatin1String("target-type"))); 0537 targetType == QLatin1String("artist")) { 0538 parseCredits(relationList, frames); 0539 } else if (targetType == QLatin1String("work")) { 0540 if (QDomNode workRelationListNode(relationList 0541 .namedItem(QLatin1String("relation")) 0542 .namedItem(QLatin1String("work")) 0543 .namedItem(QLatin1String("relation-list"))); 0544 !workRelationListNode.isNull()) { 0545 parseCredits(workRelationListNode.toElement(), frames); 0546 } 0547 } 0548 } 0549 } 0550 relationListNode = relationListNode.nextSibling(); 0551 } 0552 } 0553 } 0554 duration /= 1000; 0555 if (atTrackDataListEnd) { 0556 ImportTrackData trackData; 0557 trackData.setFrameCollection(frames); 0558 trackData.setImportDuration(duration); 0559 trackDataVector.push_back(trackData); 0560 } else { 0561 while (!atTrackDataListEnd && !it->isEnabled()) { 0562 ++it; 0563 atTrackDataListEnd = it == trackDataVector.end(); 0564 } 0565 if (!atTrackDataListEnd) { 0566 it->setFrameCollection(frames); 0567 it->setImportDuration(duration); 0568 ++it; 0569 atTrackDataListEnd = it == trackDataVector.end(); 0570 } 0571 } 0572 ++trackNr; 0573 frames = framesHdr; 0574 } 0575 ++discNr; 0576 } 0577 // handle redundant tracks 0578 frames.clear(); 0579 while (!atTrackDataListEnd) { 0580 if (it->isEnabled()) { 0581 if (it->getFileDuration() == 0) { 0582 it = trackDataVector.erase(it); 0583 } else { 0584 it->setFrameCollection(frames); 0585 it->setImportDuration(0); 0586 ++it; 0587 } 0588 } else { 0589 ++it; 0590 } 0591 atTrackDataListEnd = it == trackDataVector.end(); 0592 } 0593 m_trackDataModel->setTrackData(trackDataVector); 0594 } 0595 } 0596 0597 /** 0598 * Send a query command to search on the server. 0599 * 0600 * @param cfg import source configuration 0601 * @param artist artist to search 0602 * @param album album to search 0603 */ 0604 void MusicBrainzImporter::sendFindQuery( 0605 const ServerImporterConfig* cfg, 0606 const QString& artist, const QString& album) 0607 { 0608 Q_UNUSED(cfg) 0609 // If an URL is entered in the first search field, its result will be directly 0610 // available in the album results list. 0611 if (artist.startsWith(QLatin1String("https://musicbrainz.org/"))) { 0612 constexpr int catBegin = 24; 0613 if (int catEnd = artist.indexOf(QLatin1Char('/'), catBegin); 0614 catEnd > catBegin) { 0615 m_albumListModel->clear(); 0616 m_albumListModel->appendItem( 0617 artist, 0618 artist.mid(catBegin, catEnd - catBegin), 0619 artist.mid(catEnd + 1)); 0620 return; 0621 } 0622 } 0623 /* 0624 * Query looks like this: 0625 * http://musicbrainz.org/ws/2/release?query=artist:wizard%20AND%20release:odin 0626 */ 0627 QString path(QLatin1String("/ws/2/release?query=")); 0628 if (!artist.isEmpty()) { 0629 QString artistQuery(artist.contains(QLatin1Char(' ')) 0630 ? QLatin1Char('"') + artist + QLatin1Char('"') 0631 : artist); 0632 if (!album.isEmpty()) { 0633 artistQuery += QLatin1String(" AND "); 0634 } 0635 path += QLatin1String("artist:"); 0636 path += QString::fromLatin1(QUrl::toPercentEncoding(artistQuery)); 0637 } 0638 if (!album.isEmpty()) { 0639 QString albumQuery(album.contains(QLatin1Char(' ')) 0640 ? QLatin1Char('"') + album + QLatin1Char('"') 0641 : album); 0642 path += QLatin1String("release:"); 0643 path += QString::fromLatin1(QUrl::toPercentEncoding(albumQuery)); 0644 } 0645 sendRequest(QLatin1String("musicbrainz.org"), path, QLatin1String("https"), 0646 m_headers); 0647 } 0648 0649 /** 0650 * Send a query command to fetch the track list 0651 * from the server. 0652 * 0653 * @param cfg import source configuration 0654 * @param cat category 0655 * @param id ID 0656 */ 0657 void MusicBrainzImporter::sendTrackListQuery( 0658 const ServerImporterConfig* cfg, const QString& cat, const QString& id) 0659 { 0660 /* 0661 * Query looks like this: 0662 * http://musicbrainz.org/ws/2/release/978c7ed1-a854-4ef2-bd4e-e7c1317be854?inc=artists+recordings 0663 */ 0664 QString path(QLatin1String("/ws/2/")); 0665 path += cat; 0666 path += QLatin1Char('/'); 0667 path += id; 0668 path += QLatin1String("?inc="); 0669 if (cfg->additionalTags()) { 0670 path += QLatin1String("artist-credits+labels+recordings+genres+media+isrcs+" 0671 "discids+artist-rels+label-rels+recording-rels+release-rels"); 0672 } else { 0673 path += QLatin1String("artists+recordings+genres"); 0674 } 0675 if (cfg->coverArt()) { 0676 path += QLatin1String("+url-rels"); 0677 } 0678 if (cfg->additionalTags()) { 0679 path += QLatin1String("+work-rels+recording-level-rels+work-level-rels"); 0680 } 0681 sendRequest(QLatin1String("musicbrainz.org"), path, QLatin1String("https"), 0682 m_headers); 0683 }