1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "Album.h"
10
11 #include "FileItem.h"
12 #include "ServiceBroker.h"
13 #include "music/tags/MusicInfoTag.h"
14 #include "settings/AdvancedSettings.h"
15 #include "settings/SettingsComponent.h"
16 #include "utils/MathUtils.h"
17 #include "utils/StringUtils.h"
18 #include "utils/XMLUtils.h"
19 #include "utils/log.h"
20
21 #include <algorithm>
22
23 using namespace MUSIC_INFO;
24
25 typedef struct ReleaseTypeInfo {
26 CAlbum::ReleaseType type;
27 std::string name;
28 } ReleaseTypeInfo;
29
30 ReleaseTypeInfo releaseTypes[] = {
31 { CAlbum::Album, "album" },
32 { CAlbum::Single, "single" }
33 };
34
CAlbum(const CFileItem & item)35 CAlbum::CAlbum(const CFileItem& item)
36 {
37 Reset();
38 const CMusicInfoTag& tag = *item.GetMusicInfoTag();
39 strAlbum = tag.GetAlbum();
40 strMusicBrainzAlbumID = tag.GetMusicBrainzAlbumID();
41 strReleaseGroupMBID = tag.GetMusicBrainzReleaseGroupID();
42 genre = tag.GetGenre();
43 strArtistDesc = tag.GetAlbumArtistString();
44 //Set sort string before processing artist credits
45 strArtistSort = tag.GetAlbumArtistSort();
46 // Determine artist credits from various tag arrays, inc fallback to song artist names
47 SetArtistCredits(tag.GetAlbumArtist(), tag.GetMusicBrainzAlbumArtistHints(), tag.GetMusicBrainzAlbumArtistID(),
48 tag.GetArtist(), tag.GetMusicBrainzArtistHints(), tag.GetMusicBrainzArtistID());
49
50 strOrigReleaseDate = tag.GetOriginalDate();
51 strReleaseDate = tag.GetReleaseDate();
52 strLabel = tag.GetRecordLabel();
53 strType = tag.GetMusicBrainzReleaseType();
54 bCompilation = tag.GetCompilation();
55 iTimesPlayed = 0;
56 bBoxedSet = tag.GetBoxset();
57 dateAdded.Reset();
58 dateUpdated.Reset();
59 lastPlayed.Reset();
60 releaseType = tag.GetAlbumReleaseType();
61 strReleaseStatus = tag.GetAlbumReleaseStatus();
62 }
63
SetArtistCredits(const std::vector<std::string> & names,const std::vector<std::string> & hints,const std::vector<std::string> & mbids,const std::vector<std::string> & artistnames,const std::vector<std::string> & artisthints,const std::vector<std::string> & artistmbids)64 void CAlbum::SetArtistCredits(const std::vector<std::string>& names, const std::vector<std::string>& hints,
65 const std::vector<std::string>& mbids,
66 const std::vector<std::string>& artistnames, const std::vector<std::string>& artisthints,
67 const std::vector<std::string>& artistmbids)
68 {
69 std::vector<std::string> albumartistHints = hints;
70 //Split the artist sort string to try and get sort names for individual artists
71 auto artistSort = StringUtils::Split(strArtistSort, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
72 artistCredits.clear();
73
74 if (!mbids.empty())
75 { // Have musicbrainz artist info, so use it
76
77 // Vector of possible separators in the order least likely to be part of artist name
78 const std::vector<std::string> separators{ " feat. ", ";", ":", "|", "#", "/", ",", "&" };
79
80 // Establish tag consistency
81 // Do the number of musicbrainz ids and *both* number of names and number of hints mis-match?
82 if (albumartistHints.size() != mbids.size() && names.size() != mbids.size())
83 {
84 // Tags mis-match - report it and then try to fix
85 CLog::Log(LOGDEBUG, "Mis-match in song file albumartist tags: %i mbid %i name album: %s %s",
86 (int)mbids.size(),
87 (int)names.size(),
88 strAlbum.c_str(), strArtistDesc.c_str());
89 /*
90 Most likely we have no hints and a single artist name like "Artist1 feat. Artist2"
91 or "Composer; Conductor, Orchestra, Soloist" or "Artist1/Artist2" where the
92 expected single item separator (default = space-slash-space) as not been used.
93 Comma and slash (no spaces) are poor delimiters as could be in name e.g. AC/DC,
94 but here treat them as such in attempt to find artist names.
95 When there are hints they could be poorly formatted using unexpected separators,
96 so attempt to split them. Or we could have more hints or artist names than
97 musicbrainz so ignore them but raise warning.
98 */
99
100 // Do hints exist yet mis-match
101 if (!albumartistHints.empty() && albumartistHints.size() != mbids.size())
102 {
103 if (names.size() == mbids.size())
104 // Album artist name count matches, use that as hints
105 albumartistHints = names;
106 else if (albumartistHints.size() < mbids.size())
107 { // Try splitting the hints until have matching number
108 albumartistHints = StringUtils::SplitMulti(albumartistHints, separators, mbids.size());
109 }
110 else
111 // Extra hints, discard them.
112 albumartistHints.resize(mbids.size());
113 }
114 // Do hints not exist or still mis-match, try album artists
115 if (albumartistHints.size() != mbids.size())
116 albumartistHints = names;
117 // Still mis-match, try splitting the hints (now artists) until have matching number
118 if (albumartistHints.size() < mbids.size())
119 albumartistHints = StringUtils::SplitMulti(albumartistHints, separators, mbids.size());
120 // Try matching on artists or artist hints field, if it is reliable
121 if (albumartistHints.size() != mbids.size())
122 {
123 if (!artistmbids.empty() &&
124 (artistmbids.size() == artistnames.size() ||
125 artistmbids.size() == artisthints.size()))
126 {
127 for (size_t i = 0; i < mbids.size(); i++)
128 {
129 for (size_t j = 0; j < artistmbids.size(); j++)
130 {
131 if (mbids[i] == artistmbids[j])
132 {
133 if (albumartistHints.size() < i + 1)
134 albumartistHints.resize(i + 1);
135 if (artistmbids.size() == artisthints.size())
136 albumartistHints[i] = artisthints[j];
137 else
138 albumartistHints[i] = artistnames[j];
139 }
140 }
141 }
142 }
143 }
144 }
145 else
146 { // Either hints or album artists (or both) name matches number of musicbrainz id
147 // If hints mis-match, use album artists
148 if (albumartistHints.size() != mbids.size())
149 albumartistHints = names;
150 }
151
152 // Try to get number of artist sort names and musicbrainz ids to match. Split sort names
153 // further using multiple possible delimiters, over single separator applied in Tag loader
154 if (artistSort.size() != mbids.size())
155 artistSort = StringUtils::SplitMulti(artistSort, { ";", ":", "|", "#" });
156
157 for (size_t i = 0; i < mbids.size(); i++)
158 {
159 std::string artistId = mbids[i];
160 std::string artistName;
161 /*
162 We try and get the musicbrainz id <-> name matching from the hints and match on the same index.
163 Some album artist hints could be blank (if populated from artist or artist hints).
164 If not found, use the musicbrainz id and hope we later on can update that entry.
165 If we have more names than musicbrainz id they are ignored, but raise a warning.
166 */
167 if (i < albumartistHints.size())
168 artistName = albumartistHints[i];
169 if (artistName.empty())
170 artistName = artistId;
171
172 // Use artist sort name providing we have as many as we have mbid,
173 // otherwise something is wrong with them so ignore and leave blank
174 if (artistSort.size() == mbids.size())
175 artistCredits.emplace_back(StringUtils::Trim(artistName), StringUtils::Trim(artistSort[i]), artistId);
176 else
177 artistCredits.emplace_back(StringUtils::Trim(artistName), "", artistId);
178 }
179 }
180 else
181 {
182 /*
183 No musicbrainz album artist ids so fill artist names directly.
184 This method only called during scanning when there is a musicbrainz album id, so
185 means mbid tags are incomplete. But could also be called by JSON to SetAlbumDetails
186 Try to separate album artist names further, and trim blank space.
187 */
188 std::vector<std::string> albumArtists = names;
189 if (albumartistHints.size() > albumArtists.size())
190 // Make use of hints (ALBUMARTISTS tag), when present, to separate artist names
191 albumArtists = albumartistHints;
192 else
193 // Split album artist names further using multiple possible delimiters, over single separator applied in Tag loader
194 albumArtists = StringUtils::SplitMulti(albumArtists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicArtistSeparators);
195
196 if (artistSort.size() != albumArtists.size())
197 // Split artist sort names further using multiple possible delimiters, over single separator applied in Tag loader
198 artistSort = StringUtils::SplitMulti(artistSort, { ";", ":", "|", "#" });
199
200 for (size_t i = 0; i < albumArtists.size(); i++)
201 {
202 artistCredits.emplace_back(StringUtils::Trim(albumArtists[i]));
203 // Set artist sort name providing we have as many as we have artists,
204 // otherwise something is wrong with them so ignore rather than guess.
205 if (artistSort.size() == albumArtists.size())
206 artistCredits.back().SetSortName(StringUtils::Trim(artistSort[i]));
207 }
208 }
209 }
210
MergeScrapedAlbum(const CAlbum & source,bool override)211 void CAlbum::MergeScrapedAlbum(const CAlbum& source, bool override /* = true */)
212 {
213 /*
214 Initial scraping of album information when there is a Musicbrainz album ID derived from
215 tags is done directly using that ID, otherwise the lookup is based on album and artist names
216 but this can sometimes mis-identify the album (i.e. classical music has many "Symphony No. 5").
217 It is useful to store the scraped mbid, but we need to be able to correct any mistakes. Hence
218 a manual refresh of album information uses either the mbid as derived from tags or the album
219 and artist names, not any previously scraped mbid.
220
221 When overwritting the data derived from tags, AND the original and scraped album have the same
222 Musicbrainz album ID, then merging is used to keep Kodi up to date with changes in the Musicbrainz
223 database including album artist credits, song artist credits and song titles. However it is ony
224 appropriate when the music files are tagged with mbids, these are taken as definative, scraped
225 mbids can not be depended on in this way.
226
227 When the album is megerd in this deep way it is flagged so that the database album update is aware
228 artist credits and songs need to be updated too.
229 */
230
231 bArtistSongMerge = override && !bScrapedMBID
232 && !source.strMusicBrainzAlbumID.empty() && !strMusicBrainzAlbumID.empty()
233 && (strMusicBrainzAlbumID.compare(source.strMusicBrainzAlbumID) == 0);
234
235 /*
236 Musicbrainz album (release) ID and release group ID values derived from music file tags are
237 always taken as accurate and so can not be overwritten by a scraped value. When the album does
238 not already have an mbid or has a previously scraped mbid, merge the new scraped value,
239 flagging it as being from the scraper rather than derived from music file tags.
240 */
241 if (!source.strMusicBrainzAlbumID.empty() && (strMusicBrainzAlbumID.empty() || bScrapedMBID))
242 {
243 strMusicBrainzAlbumID = source.strMusicBrainzAlbumID;
244 bScrapedMBID = true;
245 }
246 if (!source.strReleaseGroupMBID.empty() && (strReleaseGroupMBID.empty() || bScrapedMBID))
247 {
248 strReleaseGroupMBID = source.strReleaseGroupMBID;
249 }
250
251 /*
252 Scraping can return different album artists from the originals derived from tags, even when
253 doing a lookup on artist name.
254
255 When overwritting the data derived from tags, AND the original and scraped album have the same
256 Musicbrainz album ID, then merging an album replaces both the album artsts and the song artists
257 with those scraped (providing they are not empty).
258
259 When not doing that kind of merge, for any matching artist names the Musicbrainz artist id
260 returned by the scraper can be used to populate any previously missing Musicbrainz artist id values.
261 */
262 if (bArtistSongMerge && !source.artistCredits.empty())
263 {
264 artistCredits = source.artistCredits; // Replace artists and store mbid returned by scraper
265 strArtistDesc.clear(); // @todo: set artist display string e.g. "artist1 & artist2" when scraped
266 }
267 else
268 {
269 // Compare original album artists with those scraped (ignoring order), and set any missing mbid
270 for (auto &artistCredit : artistCredits)
271 {
272 if (artistCredit.GetMusicBrainzArtistID().empty())
273 {
274 for (const auto& sourceartistCredit : source.artistCredits)
275 {
276 if (StringUtils::EqualsNoCase(artistCredit.GetArtist(), sourceartistCredit.GetArtist()))
277 {
278 artistCredit.SetMusicBrainzArtistID(sourceartistCredit.GetMusicBrainzArtistID());
279 artistCredit.SetScrapedMBID(true);
280 break;
281 }
282 }
283 }
284 }
285 }
286
287 //@todo: scraped album genre needs adding to genre and album_genre tables, this just changes the string
288 if ((override && !source.genre.empty()) || genre.empty())
289 genre = source.genre;
290 if ((override && !source.strAlbum.empty()) || strAlbum.empty())
291 strAlbum = source.strAlbum;
292 //@todo: validate ISO8601 format YYYY, YYYY-MM, or YYYY-MM-DD
293 if ((override && !source.strReleaseDate.empty()) || strReleaseDate.empty())
294 strReleaseDate = source.strReleaseDate;
295 if ((override && !source.strOrigReleaseDate.empty()) || strOrigReleaseDate.empty())
296 strOrigReleaseDate = source.strOrigReleaseDate;
297
298 if (override)
299 bCompilation = source.bCompilation;
300 // iTimesPlayed = source.iTimesPlayed; // times played is derived from songs
301
302 if ((override && !source.strArtistSort.empty()) || strArtistSort.empty())
303 strArtistSort = source.strArtistSort;
304 for (const auto& i : source.art)
305 {
306 if (override || art.find(i.first) == art.end())
307 art[i.first] = i.second;
308 }
309 if((override && !source.strLabel.empty()) || strLabel.empty())
310 strLabel = source.strLabel;
311 thumbURL = source.thumbURL;
312 moods = source.moods;
313 styles = source.styles;
314 themes = source.themes;
315 strReview = source.strReview;
316 if ((override && !source.strType.empty()) || strType.empty())
317 strType = source.strType;
318 // strPath = source.strPath; // don't merge the path
319 if ((override && !source.strReleaseStatus.empty()) || strReleaseStatus.empty())
320 strReleaseStatus = source.strReleaseStatus;
321 fRating = source.fRating;
322 iUserrating = source.iUserrating;
323 iVotes = source.iVotes;
324
325 /*
326 When overwritting the data derived from tags, AND the original and scraped album have the same
327 Musicbrainz album ID, update the local songs with scaped Musicbrainz information including the
328 artist credits.
329 */
330 if (bArtistSongMerge)
331 {
332 for (auto &song : songs)
333 {
334 if (!song.strMusicBrainzTrackID.empty())
335 for (const auto& sourceSong : source.songs)
336 if ((sourceSong.strMusicBrainzTrackID == song.strMusicBrainzTrackID) && (sourceSong.iTrack == song.iTrack))
337 song.MergeScrapedSong(sourceSong, override);
338 }
339 }
340 }
341
GetGenreString() const342 std::string CAlbum::GetGenreString() const
343 {
344 return StringUtils::Join(genre, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
345 }
346
GetAlbumArtist() const347 const std::vector<std::string> CAlbum::GetAlbumArtist() const
348 {
349 //Get artist names as vector from artist credits
350 std::vector<std::string> albumartists;
351 for (const auto& artistCredit : artistCredits)
352 {
353 albumartists.push_back(artistCredit.GetArtist());
354 }
355 return albumartists;
356 }
357
GetMusicBrainzAlbumArtistID() const358 const std::vector<std::string> CAlbum::GetMusicBrainzAlbumArtistID() const
359 {
360 //Get artist MusicBrainz IDs as vector from artist credits
361 std::vector<std::string> musicBrainzID;
362 for (const auto& artistCredit : artistCredits)
363 {
364 musicBrainzID.push_back(artistCredit.GetMusicBrainzArtistID());
365 }
366 return musicBrainzID;
367 }
368
GetAlbumArtistString() const369 const std::string CAlbum::GetAlbumArtistString() const
370 {
371 //Artist description may be different from the artists in artistcredits (see ALBUMARTISTS tag processing)
372 //but is takes precedence as a string because artistcredits is not always filled during processing
373 if (!strArtistDesc.empty())
374 return strArtistDesc;
375 std::vector<std::string> artistvector;
376 for (const auto& i : artistCredits)
377 artistvector.emplace_back(i.GetArtist());
378 std::string artistString;
379 if (!artistvector.empty())
380 artistString = StringUtils::Join(artistvector, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
381 return artistString;
382 }
383
GetAlbumArtistSort() const384 const std::string CAlbum::GetAlbumArtistSort() const
385 {
386 //The stored artist sort name string takes precidence but a
387 //value could be created from individual sort names held in artistcredits
388 if (!strArtistSort.empty())
389 return strArtistSort;
390 std::vector<std::string> artistvector;
391 for (const auto& artistcredit : artistCredits)
392 if (!artistcredit.GetSortName().empty())
393 artistvector.emplace_back(artistcredit.GetSortName());
394 std::string artistString;
395 if (!artistvector.empty())
396 artistString = StringUtils::Join(artistvector, "; ");
397 return artistString;
398 }
399
GetArtistIDArray() const400 const std::vector<int> CAlbum::GetArtistIDArray() const
401 {
402 // Get album artist IDs for json rpc
403 std::vector<int> artistids;
404 for (const auto& artistCredit : artistCredits)
405 artistids.push_back(artistCredit.GetArtistId());
406 return artistids;
407 }
408
409
GetReleaseType() const410 std::string CAlbum::GetReleaseType() const
411 {
412 return ReleaseTypeToString(releaseType);
413 }
414
SetReleaseType(const std::string & strReleaseType)415 void CAlbum::SetReleaseType(const std::string& strReleaseType)
416 {
417 releaseType = ReleaseTypeFromString(strReleaseType);
418 }
419
SetDateAdded(const std::string & strDateAdded)420 void CAlbum::SetDateAdded(const std::string& strDateAdded)
421 {
422 dateAdded.SetFromDBDateTime(strDateAdded);
423 }
424
SetDateUpdated(const std::string & strDateUpdated)425 void CAlbum::SetDateUpdated(const std::string& strDateUpdated)
426 {
427 dateUpdated.SetFromDBDateTime(strDateUpdated);
428 }
429
SetDateNew(const std::string & strDateNew)430 void CAlbum::SetDateNew(const std::string& strDateNew)
431 {
432 dateNew.SetFromDBDateTime(strDateNew);
433 }
434
SetLastPlayed(const std::string & strLastPlayed)435 void CAlbum::SetLastPlayed(const std::string& strLastPlayed)
436 {
437 lastPlayed.SetFromDBDateTime(strLastPlayed);
438 }
439
ReleaseTypeToString(CAlbum::ReleaseType releaseType)440 std::string CAlbum::ReleaseTypeToString(CAlbum::ReleaseType releaseType)
441 {
442 for (const ReleaseTypeInfo& releaseTypeInfo : releaseTypes)
443 {
444 if (releaseTypeInfo.type == releaseType)
445 return releaseTypeInfo.name;
446 }
447
448 return "album";
449 }
450
ReleaseTypeFromString(const std::string & strReleaseType)451 CAlbum::ReleaseType CAlbum::ReleaseTypeFromString(const std::string& strReleaseType)
452 {
453 for (const ReleaseTypeInfo& releaseTypeInfo : releaseTypes)
454 {
455 if (releaseTypeInfo.name == strReleaseType)
456 return releaseTypeInfo.type;
457 }
458
459 return Album;
460 }
461
operator <(const CAlbum & a) const462 bool CAlbum::operator<(const CAlbum &a) const
463 {
464 if (strMusicBrainzAlbumID.empty() && a.strMusicBrainzAlbumID.empty())
465 {
466 if (strAlbum < a.strAlbum) return true;
467 if (strAlbum > a.strAlbum) return false;
468
469 // This will do an std::vector compare (i.e. item by item)
470 if (GetAlbumArtist() < a.GetAlbumArtist()) return true;
471 if (GetAlbumArtist() > a.GetAlbumArtist()) return false;
472 return false;
473 }
474
475 if (strMusicBrainzAlbumID < a.strMusicBrainzAlbumID) return true;
476 if (strMusicBrainzAlbumID > a.strMusicBrainzAlbumID) return false;
477 return false;
478 }
479
Load(const TiXmlElement * album,bool append,bool prioritise)480 bool CAlbum::Load(const TiXmlElement *album, bool append, bool prioritise)
481 {
482 if (!album) return false;
483 if (!append)
484 Reset();
485
486 const std::string itemSeparator = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator;
487
488 XMLUtils::GetString(album, "title", strAlbum);
489 XMLUtils::GetString(album, "musicbrainzalbumid", strMusicBrainzAlbumID);
490 XMLUtils::GetString(album, "musicbrainzreleasegroupid", strReleaseGroupMBID);
491 XMLUtils::GetBoolean(album, "scrapedmbid", bScrapedMBID);
492 XMLUtils::GetString(album, "artistdesc", strArtistDesc);
493 std::vector<std::string> artist; // Support old style <artist></artist> for backwards compatibility
494 XMLUtils::GetStringArray(album, "artist", artist, prioritise, itemSeparator);
495 XMLUtils::GetStringArray(album, "genre", genre, prioritise, itemSeparator);
496 XMLUtils::GetStringArray(album, "style", styles, prioritise, itemSeparator);
497 XMLUtils::GetStringArray(album, "mood", moods, prioritise, itemSeparator);
498 XMLUtils::GetStringArray(album, "theme", themes, prioritise, itemSeparator);
499 XMLUtils::GetBoolean(album, "compilation", bCompilation);
500 XMLUtils::GetBoolean(album, "boxset", bBoxedSet);
501
502 XMLUtils::GetString(album,"review",strReview);
503 XMLUtils::GetString(album,"label",strLabel);
504 XMLUtils::GetInt(album, "duration", iAlbumDuration);
505 XMLUtils::GetString(album,"type",strType);
506 XMLUtils::GetString(album, "releasestatus", strReleaseStatus);
507
508 XMLUtils::GetString(album, "releasedate", strReleaseDate);
509 StringUtils::Trim(strReleaseDate); // @todo: validate ISO8601 format
510 // Support old style <year></year> for backwards compatibility
511 if (strReleaseDate.empty())
512 {
513 int year;
514 XMLUtils::GetInt(album, "year", year);
515 if (year > 0)
516 strReleaseDate = StringUtils::Format("%04i", year);
517 }
518 XMLUtils::GetString(album, "originalreleasedate", strOrigReleaseDate);
519
520 const TiXmlElement* rElement = album->FirstChildElement("rating");
521 if (rElement)
522 {
523 float rating = 0;
524 float max_rating = 10;
525 XMLUtils::GetFloat(album, "rating", rating);
526 if (rElement->QueryFloatAttribute("max", &max_rating) == TIXML_SUCCESS && max_rating>=1)
527 rating *= (10.f / max_rating); // Normalise the Rating to between 0 and 10
528 if (rating > 10.f)
529 rating = 10.f;
530 fRating = rating;
531 }
532 const TiXmlElement* userrating = album->FirstChildElement("userrating");
533 if (userrating)
534 {
535 float rating = 0;
536 float max_rating = 10;
537 XMLUtils::GetFloat(album, "userrating", rating);
538 if (userrating->QueryFloatAttribute("max", &max_rating) == TIXML_SUCCESS && max_rating >= 1)
539 rating *= (10.f / max_rating); // Normalise the Rating to between 0 and 10
540 if (rating > 10.f)
541 rating = 10.f;
542 iUserrating = MathUtils::round_int(rating);
543 }
544 XMLUtils::GetInt(album, "votes", iVotes);
545
546 size_t iThumbCount = thumbURL.GetUrls().size();
547 std::string xmlAdd = thumbURL.GetData();
548 const TiXmlElement* thumb = album->FirstChildElement("thumb");
549 while (thumb)
550 {
551 thumbURL.ParseAndAppendUrl(thumb);
552 if (prioritise)
553 {
554 std::string temp;
555 temp << *thumb;
556 xmlAdd = temp+xmlAdd;
557 }
558 thumb = thumb->NextSiblingElement("thumb");
559 }
560 // prioritise thumbs from nfos
561 if (prioritise && iThumbCount && iThumbCount != thumbURL.GetUrls().size())
562 {
563 auto thumbUrls = thumbURL.GetUrls();
564 rotate(thumbUrls.begin(), thumbUrls.begin() + iThumbCount, thumbUrls.end());
565 thumbURL.SetUrls(thumbUrls);
566 thumbURL.SetData(xmlAdd);
567 }
568
569 const TiXmlElement* albumArtistCreditsNode = album->FirstChildElement("albumArtistCredits");
570 if (albumArtistCreditsNode)
571 artistCredits.clear();
572
573 while (albumArtistCreditsNode)
574 {
575 if (albumArtistCreditsNode->FirstChild())
576 {
577 CArtistCredit artistCredit;
578 XMLUtils::GetString(albumArtistCreditsNode, "artist", artistCredit.m_strArtist);
579 XMLUtils::GetString(albumArtistCreditsNode, "musicBrainzArtistID", artistCredit.m_strMusicBrainzArtistID);
580 artistCredits.push_back(artistCredit);
581 }
582
583 albumArtistCreditsNode = albumArtistCreditsNode->NextSiblingElement("albumArtistCredits");
584 }
585
586 // Support old style <artist></artist> for backwards compatibility
587 // .nfo files should ideally be updated to use the artist credits structure above
588 // or removed entirely in preference for better tags (MusicBrainz?)
589 if (artistCredits.empty() && !artist.empty())
590 {
591 for (const auto& it : artist)
592 {
593 CArtistCredit artistCredit(it);
594 artistCredits.push_back(artistCredit);
595 }
596 }
597
598 std::string strReleaseType;
599 if (XMLUtils::GetString(album, "releasetype", strReleaseType))
600 SetReleaseType(strReleaseType);
601 else
602 releaseType = Album;
603
604 return true;
605 }
606
Save(TiXmlNode * node,const std::string & tag,const std::string & strPath)607 bool CAlbum::Save(TiXmlNode *node, const std::string &tag, const std::string& strPath)
608 {
609 if (!node) return false;
610
611 // we start with a <tag> tag
612 TiXmlElement albumElement(tag.c_str());
613 TiXmlNode *album = node->InsertEndChild(albumElement);
614
615 if (!album) return false;
616
617 XMLUtils::SetString(album, "title", strAlbum);
618 XMLUtils::SetString(album, "musicbrainzalbumid", strMusicBrainzAlbumID);
619 XMLUtils::SetString(album, "musicbrainzreleasegroupid", strReleaseGroupMBID);
620 XMLUtils::SetBoolean(album, "scrapedmbid", bScrapedMBID);
621 XMLUtils::SetString(album, "artistdesc", strArtistDesc); //Can be different from artist credits
622 XMLUtils::SetStringArray(album, "genre", genre);
623 XMLUtils::SetStringArray(album, "style", styles);
624 XMLUtils::SetStringArray(album, "mood", moods);
625 XMLUtils::SetStringArray(album, "theme", themes);
626 XMLUtils::SetBoolean(album, "compilation", bCompilation);
627 XMLUtils::SetBoolean(album, "boxset", bBoxedSet);
628
629 XMLUtils::SetString(album, "review", strReview);
630 XMLUtils::SetString(album, "type", strType);
631 XMLUtils::SetString(album, "releasestatus", strReleaseStatus);
632 XMLUtils::SetString(album, "releasedate", strReleaseDate);
633 XMLUtils::SetString(album, "originalreleasedate", strOrigReleaseDate);
634 XMLUtils::SetString(album, "label", strLabel);
635 XMLUtils::SetInt(album, "duration", iAlbumDuration);
636 if (thumbURL.HasData())
637 {
638 CXBMCTinyXML doc;
639 doc.Parse(thumbURL.GetData());
640 const TiXmlNode* thumb = doc.FirstChild("thumb");
641 while (thumb)
642 {
643 album->InsertEndChild(*thumb);
644 thumb = thumb->NextSibling("thumb");
645 }
646 }
647 XMLUtils::SetString(album, "path", strPath);
648
649 auto* rating = XMLUtils::SetFloat(album, "rating", fRating);
650 if (rating)
651 rating->ToElement()->SetAttribute("max", 10);
652
653 auto* userrating = XMLUtils::SetInt(album, "userrating", iUserrating);
654 if (userrating)
655 userrating->ToElement()->SetAttribute("max", 10);
656
657 XMLUtils::SetInt(album, "votes", iVotes);
658
659 for (const auto& artistCredit : artistCredits)
660 {
661 // add an <albumArtistCredits> tag
662 TiXmlElement albumArtistCreditsElement("albumArtistCredits");
663 TiXmlNode *albumArtistCreditsNode = album->InsertEndChild(albumArtistCreditsElement);
664 XMLUtils::SetString(albumArtistCreditsNode, "artist", artistCredit.m_strArtist);
665 XMLUtils::SetString(albumArtistCreditsNode, "musicBrainzArtistID",
666 artistCredit.m_strMusicBrainzArtistID);
667 }
668
669 XMLUtils::SetString(album, "releasetype", GetReleaseType());
670
671 return true;
672 }
673
674