12 #ifdef HAVE_MUSICBRAINZ
18 #include <discid/discid.h>
21 #include "musicbrainz5/Artist.h"
22 #include "musicbrainz5/ArtistCredit.h"
23 #include "musicbrainz5/NameCredit.h"
24 #include "musicbrainz5/Query.h"
25 #include "musicbrainz5/Disc.h"
26 #include "musicbrainz5/Medium.h"
27 #include "musicbrainz5/Release.h"
28 #include "musicbrainz5/Track.h"
29 #include "musicbrainz5/TrackList.h"
30 #include "musicbrainz5/Recording.h"
31 #include "musicbrainz5/HTTPFetch.h"
34 #include "coverart/CoverArt.h"
35 #include "coverart/HTTPFetch.h"
39 std::string MusicBrainz::queryDiscId(
const std::string &device)
41 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Query disc id for device %1").arg(QString::fromStdString(device)));
42 DiscId *disc = discid_new();
44 if ( discid_read_sparse(disc, device.c_str(), 0) == 0 )
46 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: %1").arg(discid_get_error_msg(disc)));
50 disc_id = discid_get_id(disc);
51 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Got disc id %1").arg(QString::fromStdString(disc_id)));
59 static std::vector<std::string> queryArtists(
const MusicBrainz5::CArtistCredit *artist_credit)
61 std::vector<std::string> artist_names;
67 std::string joinPhrase;
68 for (
int a = 0; a < artist_credit->NameCreditList()->NumItems(); ++a)
70 auto *nameCredit = artist_credit->NameCreditList()->Item(a);
71 auto *artist = nameCredit->Artist();
74 joinPhrase = nameCredit->JoinPhrase();
75 artist_names.emplace_back(artist->Name());
77 else if (!joinPhrase.empty())
79 artist_names.back() += joinPhrase + artist->Name();
83 artist_names.emplace_back(artist->Name());
90 static QString artistsToString(
const std::vector<std::string> &artists)
93 for (
const auto &artist : artists)
95 res += QString(res.isEmpty() ?
"%1" :
"; %1").arg(artist.c_str());
101 static void logError(MusicBrainz5::CQuery &query)
103 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: LastResult: %1").arg(query.LastResult()));
104 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: LastHTTPCode: %1").arg(query.LastHTTPCode()));
105 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: LastErrorMessage: '%1'").arg(QString::fromStdString(query.LastErrorMessage())));
108 std::string MusicBrainz::queryRelease(
const std::string &discId)
113 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Query metadata for disc id '%1'").arg(QString::fromStdString(discId)));
117 auto discMetadata = query.Query(
"discid", discId);
118 if (discMetadata.Disc() && discMetadata.Disc()->ReleaseList())
120 auto *releases = discMetadata.Disc()->ReleaseList();
121 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Found %1 release(s)").arg(releases->NumItems()));
122 for (
int count = 0; count < releases->NumItems(); ++count)
124 auto *basicRelease = releases->Item(count);
126 MusicBrainz5::CQuery::tParamMap params;
127 params[
"inc"]=
"artists recordings artist-credits discids";
128 auto releaseMetadata = query.Query(
"release", basicRelease->ID(),
"", params);
129 if (releaseMetadata.Release())
131 auto *fullRelease = releaseMetadata.Release();
136 auto media = fullRelease->MediaMatchingDiscID(discId);
137 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Found %1 matching media").arg(media.NumItems()));
139 for (
int m = 0; m < media.NumItems(); ++m)
141 auto *medium = media.Item(m);
142 if (!medium || !medium->ContainsDiscID(discId))
146 std::string albumTitle;
147 if (!medium->Title().empty())
149 albumTitle = medium->Title();
151 else if(!fullRelease->Title().empty())
153 albumTitle = fullRelease->Title();
155 const auto albumArtists = queryArtists(fullRelease->ArtistCredit());
156 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Release: %1").arg(QString::fromStdString(fullRelease->ID())));
157 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Title: %1").arg(QString::fromStdString(albumTitle)));
158 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Artist: %1").arg(artistsToString(albumArtists)));
159 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Date: %1").arg(QString::fromStdString(fullRelease->Date())));
160 auto *tracks = medium->TrackList();
163 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Found %1 track(s)").arg(tracks->NumItems()));
164 for (
int t = 0;
t < tracks->NumItems(); ++
t)
166 auto *track = tracks->Item(
t);
167 if (track && track->Recording())
169 auto *recording = track->Recording();
170 const auto length = std::div(recording->Length() / 1000, 60);
171 const int minutes = length.quot;
172 const int seconds = length.rem;
173 const auto artists = queryArtists(recording->ArtistCredit());
174 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: %1: %2:%3 - %4 (%5)")
175 .arg(track->Position())
176 .arg(minutes, 2).arg(seconds, 2, 10, QChar(
'0'))
177 .arg(QString::fromStdString(recording->Title()),
178 artistsToString(artists)));
182 metadata.
setAlbum(QString::fromStdString(albumTitle));
183 metadata.
setTitle(QString::fromStdString(recording->Title()));
184 metadata.
setTrack(track->Position());
185 metadata.
setLength(std::chrono::milliseconds(recording->Length()));
186 if (albumArtists.size() == 1)
190 else if(albumArtists.size() > 1)
194 if (artists.size() == 1)
196 metadata.
setArtist(QString::fromStdString(artists[0]));
198 else if(artists.size() > 1)
200 metadata.
setArtist(QObject::tr(
"Various Artists"));
216 return fullRelease->ID();
221 catch (MusicBrainz5::CConnectionError&
error)
223 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: Connection Exception: '%1'").arg(
error.what()));
226 catch (MusicBrainz5::CTimeoutError&
error)
228 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: Timeout Exception: '%1'").arg(
error.what()));
231 catch (MusicBrainz5::CAuthenticationError&
error)
233 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: Authentication Exception: '%1'").arg(
error.what()));
236 catch (MusicBrainz5::CFetchError&
error)
238 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: Fetch Exception: '%1'").arg(
error.what()));
241 catch (MusicBrainz5::CRequestError&
error)
243 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: Request Exception: '%1'").arg(
error.what()));
246 catch (MusicBrainz5::CResourceNotFoundError&
error)
248 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: ResourceNotFound Exception: '%1'").arg(
error.what()));
257 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Setting compilation flag: %1").arg(isCompilation));
264 static void logError(CoverArtArchive::CCoverArt &coverArt)
266 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: LastResult: %1").arg(coverArt.LastResult()));
267 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: LastHTTPCode: %1").arg(coverArt.LastHTTPCode()));
268 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: LastErrorMessage: '%1'").arg(QString::fromStdString(coverArt.LastErrorMessage())));
271 QString MusicBrainz::queryCoverart(
const std::string &releaseId)
273 const QString fileName = QString(
"musicbrainz-%1-front.jpg").arg(releaseId.c_str());
274 QString filePath = QDir::temp().absoluteFilePath(fileName);
275 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Check if coverart file exists for release '%1'").arg(QString::fromStdString(releaseId)));
276 if (QDir::temp().
exists(fileName))
278 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Cover art file '%1' exist already").arg(filePath));
282 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Query cover art for release '%1'").arg(QString::fromStdString(releaseId)));
283 CoverArtArchive::CCoverArt coverArt(
user_agent);
286 std::vector<unsigned char> imageData = coverArt.FetchFront(releaseId);
287 if (!imageData.empty())
289 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Saving front coverart to '%1'").arg(filePath));
291 QFile coverArtFile(filePath);
292 if (!coverArtFile.open(QIODevice::WriteOnly))
294 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: Unable to open temporary file '%1'").arg(filePath));
298 const auto coverArtBytes =
static_cast<qint64
>(imageData.size());
299 const auto writtenBytes = coverArtFile.write(
reinterpret_cast<const char*
>(imageData.data()), coverArtBytes);
300 coverArtFile.close();
301 if (writtenBytes != coverArtBytes)
303 LOG(VB_MEDIA, LOG_ERR, QString(
"ERROR musicbrainz: Could not write coverart data to file '%1'").arg(filePath));
310 catch (CoverArtArchive::CConnectionError&
error)
312 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: Connection Exception: '%1'").arg(
error.what()));
315 catch (CoverArtArchive::CTimeoutError&
error)
317 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: Timeout Exception: '%1'").arg(
error.what()));
320 catch (CoverArtArchive::CAuthenticationError&
error)
322 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: Authentication Exception: '%1'").arg(
error.what()));
325 catch (CoverArtArchive::CFetchError&
error)
327 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: Fetch Exception: '%1'").arg(
error.what()));
330 catch (CoverArtArchive::CRequestError&
error)
332 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: Request Exception: '%1'").arg(
error.what()));
335 catch (CoverArtArchive::CResourceNotFoundError&
error)
337 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: ResourceNotFound Exception: '%1'").arg(
error.what()));
344 #endif // HAVE_MUSICBRAINZ
348 #ifdef HAVE_MUSICBRAINZ
349 const auto discId = queryDiscId(deviceName.toStdString());
355 if (discId == m_discId)
358 LOG(VB_MEDIA, LOG_DEBUG, QString(
"musicbrainz: Metadata for disc %1 already present").arg(QString::fromStdString(m_discId)));
365 const auto releaseId = queryRelease(discId);
366 if (releaseId.empty())
370 const auto covertArtFileName = queryCoverart(releaseId);
371 if (!covertArtFileName.isEmpty())
394 LOG(VB_MEDIA, LOG_ERR, QString(
"musicbrainz: No metadata for track %1").arg(track));
407 LOG(VB_MEDIA, LOG_DEBUG,
"musicbrainz: Reset metadata");