Merge pull request #4857 from t-nelson/Gotham_13.2_backports
[vuplus_xbmc] / xbmc / music / Album.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "Album.h"
22 #include "settings/AdvancedSettings.h"
23 #include "utils/StringUtils.h"
24 #include "utils/XMLUtils.h"
25 #include "utils/MathUtils.h"
26 #include "FileItem.h"
27
28 using namespace std;
29 using namespace MUSIC_INFO;
30
31 CAlbum::CAlbum(const CFileItem& item)
32 {
33   Reset();
34   const CMusicInfoTag& tag = *item.GetMusicInfoTag();
35   SYSTEMTIME stTime;
36   tag.GetReleaseDate(stTime);
37   strAlbum = tag.GetAlbum();
38   strMusicBrainzAlbumID = tag.GetMusicBrainzAlbumID();
39   genre = tag.GetGenre();
40   artist = tag.GetAlbumArtist();
41   if (!tag.GetMusicBrainzAlbumArtistID().empty())
42   { // have musicbrainz artist info, so use it
43     for (size_t i = 0; i < tag.GetMusicBrainzAlbumArtistID().size(); i++)
44     {
45       CStdString artistId = tag.GetMusicBrainzAlbumArtistID()[i];
46       CStdString artistName;
47       /*
48        We try and get the corresponding artist name from the album artist tag.
49        We match on the same index, and if that fails just use the first name we have.
50        If no albumartist exists, try matching on artist if the MBArtistID matches.
51        */
52       if (!artist.empty())
53         artistName = (i < artist.size()) ? artist[i] : artist[0];
54       else if (!tag.GetMusicBrainzArtistID().empty() && !tag.GetArtist().empty())
55       {
56         vector<string>::const_iterator j = std::find(tag.GetMusicBrainzArtistID().begin(), tag.GetMusicBrainzArtistID().end(), artistId);
57         if (j != tag.GetMusicBrainzArtistID().end())
58         { // find corresponding artist
59           size_t d = std::distance(j,tag.GetMusicBrainzArtistID().begin());
60           artistName = (d < tag.GetArtist().size()) ? tag.GetArtist()[d] : tag.GetArtist()[0];
61         }
62       }
63       if (artistName.empty())
64         artistName = artistId;
65       CStdString strJoinPhrase = (i == tag.GetMusicBrainzAlbumArtistID().size()-1) ? "" : g_advancedSettings.m_musicItemSeparator;
66       CArtistCredit artistCredit(artistName, tag.GetMusicBrainzAlbumArtistID()[i], strJoinPhrase);
67       artistCredits.push_back(artistCredit);
68     }
69   }
70   else
71   { // no musicbrainz info, so fill in directly
72     for (vector<string>::const_iterator it = tag.GetAlbumArtist().begin(); it != tag.GetAlbumArtist().end(); ++it)
73     {
74       CStdString strJoinPhrase = (it == --tag.GetAlbumArtist().end() ? "" : g_advancedSettings.m_musicItemSeparator);
75       CArtistCredit artistCredit(*it, "", strJoinPhrase);
76       artistCredits.push_back(artistCredit);
77     }
78   }
79   iYear = stTime.wYear;
80   bCompilation = tag.GetCompilation();
81   iTimesPlayed = 0;
82 }
83
84 void CAlbum::MergeScrapedAlbum(const CAlbum& source, bool override /* = true */)
85 {
86   /*
87    We don't merge musicbrainz album ID so that a refresh of album information
88    allows a lookup based on name rather than directly (re)using musicbrainz.
89    In future, we may wish to be able to override lookup by musicbrainz so
90    this might be dropped.
91    */
92 //  strMusicBrainzAlbumID = source.strMusicBrainzAlbumID;
93   if ((override && !source.genre.empty()) || genre.empty())
94     genre = source.genre;
95   if ((override && !source.strAlbum.empty()) || strAlbum.empty())
96     strAlbum = source.strAlbum;
97   if ((override && source.iYear > 0) || iYear == 0)
98     iYear = source.iYear;
99   if (override)
100     bCompilation = source.bCompilation;
101   //  iTimesPlayed = source.iTimesPlayed; // times played is derived from songs
102   for (std::map<std::string, std::string>::const_iterator i = source.art.begin(); i != source.art.end(); ++i)
103   {
104     if (override || art.find(i->first) == art.end())
105       art[i->first] = i->second;
106   }
107   strLabel = source.strLabel;
108   thumbURL = source.thumbURL;
109   moods = source.moods;
110   styles = source.styles;
111   themes = source.themes;
112   strReview = source.strReview;
113   strType = source.strType;
114 //  strPath = source.strPath; // don't merge the path
115   m_strDateOfRelease = source.m_strDateOfRelease;
116   iRating = source.iRating;
117   if (override)
118   {
119     artistCredits = source.artistCredits;
120     artist = source.artist; // artist information is read-only from the database. artistCredits is what counts on scan
121   }
122   else if (source.artistCredits.size() > artistCredits.size())
123     artistCredits.insert(artistCredits.end(), source.artistCredits.begin()+artistCredits.size(), source.artistCredits.end());
124   if (!strMusicBrainzAlbumID.empty())
125   {
126     /* update local songs with MB information */
127     for (VECSONGS::iterator song = songs.begin(); song != songs.end(); ++song)
128     {
129       if (!song->strMusicBrainzTrackID.empty())
130         for (VECSONGS::const_iterator sourceSong = source.infoSongs.begin(); sourceSong != source.infoSongs.end(); ++sourceSong)
131           if (sourceSong->strMusicBrainzTrackID == song->strMusicBrainzTrackID)
132             song->MergeScrapedSong(*sourceSong, override);
133     }
134   }
135   infoSongs = source.infoSongs;
136 }
137
138 CStdString CAlbum::GetArtistString() const
139 {
140   return StringUtils::Join(artist, g_advancedSettings.m_musicItemSeparator);
141 }
142
143 CStdString CAlbum::GetGenreString() const
144 {
145   return StringUtils::Join(genre, g_advancedSettings.m_musicItemSeparator);
146 }
147
148 bool CAlbum::operator<(const CAlbum &a) const
149 {
150   if (strMusicBrainzAlbumID.empty() && a.strMusicBrainzAlbumID.empty())
151   {
152     if (strAlbum < a.strAlbum) return true;
153     if (strAlbum > a.strAlbum) return false;
154
155     // This will do an std::vector compare (i.e. item by item)
156     if (artist < a.artist) return true;
157     if (artist > a.artist) return false;
158     return false;
159   }
160
161   if (strMusicBrainzAlbumID < a.strMusicBrainzAlbumID) return true;
162   if (strMusicBrainzAlbumID > a.strMusicBrainzAlbumID) return false;
163   return false;
164 }
165
166 bool CAlbum::Load(const TiXmlElement *album, bool append, bool prioritise)
167 {
168   if (!album) return false;
169   if (!append)
170     Reset();
171
172   XMLUtils::GetString(album,              "title", strAlbum);
173   XMLUtils::GetString(album, "musicBrainzAlbumID", strMusicBrainzAlbumID);
174
175   XMLUtils::GetStringArray(album, "artist", artist, prioritise, g_advancedSettings.m_musicItemSeparator);
176   XMLUtils::GetStringArray(album, "genre", genre, prioritise, g_advancedSettings.m_musicItemSeparator);
177   XMLUtils::GetStringArray(album, "style", styles, prioritise, g_advancedSettings.m_musicItemSeparator);
178   XMLUtils::GetStringArray(album, "mood", moods, prioritise, g_advancedSettings.m_musicItemSeparator);
179   XMLUtils::GetStringArray(album, "theme", themes, prioritise, g_advancedSettings.m_musicItemSeparator);
180   XMLUtils::GetBoolean(album, "compilation", bCompilation);
181
182   XMLUtils::GetString(album,"review",strReview);
183   XMLUtils::GetString(album,"releasedate",m_strDateOfRelease);
184   XMLUtils::GetString(album,"label",strLabel);
185   XMLUtils::GetString(album,"type",strType);
186
187   XMLUtils::GetInt(album,"year",iYear);
188   const TiXmlElement* rElement = album->FirstChildElement("rating");
189   if (rElement)
190   {
191     float rating = 0;
192     float max_rating = 5;
193     XMLUtils::GetFloat(album, "rating", rating);
194     if (rElement->QueryFloatAttribute("max", &max_rating) == TIXML_SUCCESS && max_rating>=1)
195       rating *= (5.f / max_rating); // Normalise the Rating to between 0 and 5 
196     if (rating > 5.f)
197       rating = 5.f;
198     iRating = MathUtils::round_int(rating);
199   }
200
201   size_t iThumbCount = thumbURL.m_url.size();
202   CStdString xmlAdd = thumbURL.m_xml;
203   const TiXmlElement* thumb = album->FirstChildElement("thumb");
204   while (thumb)
205   {
206     thumbURL.ParseElement(thumb);
207     if (prioritise)
208     {
209       CStdString temp;
210       temp << *thumb;
211       xmlAdd = temp+xmlAdd;
212     }
213     thumb = thumb->NextSiblingElement("thumb");
214   }
215   // prioritise thumbs from nfos
216   if (prioritise && iThumbCount && iThumbCount != thumbURL.m_url.size())
217   {
218     rotate(thumbURL.m_url.begin(),
219            thumbURL.m_url.begin()+iThumbCount, 
220            thumbURL.m_url.end());
221     thumbURL.m_xml = xmlAdd;
222   }
223
224   const TiXmlElement* albumArtistCreditsNode = album->FirstChildElement("albumArtistCredits");
225   if (albumArtistCreditsNode)
226     artistCredits.clear();
227
228   while (albumArtistCreditsNode)
229   {
230     if (albumArtistCreditsNode->FirstChild())
231     {
232       CArtistCredit artistCredit;
233       XMLUtils::GetString(albumArtistCreditsNode,  "artist",               artistCredit.m_strArtist);
234       XMLUtils::GetString(albumArtistCreditsNode,  "musicBrainzArtistID",  artistCredit.m_strMusicBrainzArtistID);
235       XMLUtils::GetString(albumArtistCreditsNode,  "joinphrase",           artistCredit.m_strJoinPhrase);
236       XMLUtils::GetBoolean(albumArtistCreditsNode, "featuring",            artistCredit.m_boolFeatured);
237       artistCredits.push_back(artistCredit);
238     }
239
240     albumArtistCreditsNode = albumArtistCreditsNode->NextSiblingElement("albumArtistCredits");
241   }
242
243   // Support old style <artist></artist> for backwards compatibility
244   // .nfo files should ideally be updated to use the artist credits structure above
245   // or removed entirely in preference for better tags (MusicBrainz?)
246   if (artistCredits.empty() && !artist.empty())
247   {
248     for (vector<string>::const_iterator it = artist.begin(); it != artist.end(); ++it)
249     {
250       CArtistCredit artistCredit(*it, StringUtils::EmptyString,
251                                  it == --artist.end() ? StringUtils::EmptyString : g_advancedSettings.m_musicItemSeparator);
252       artistCredits.push_back(artistCredit);
253     }
254   }
255
256   const TiXmlElement* node = album->FirstChildElement("track");
257   if (node)
258     infoSongs.clear();  // this means that the tracks can't be spread over separate pages
259                     // but this is probably a reasonable limitation
260   bool bIncrement = false;
261   while (node)
262   {
263     if (node->FirstChild())
264     {
265
266       CSong song;
267       const TiXmlElement* songArtistCreditsNode = node->FirstChildElement("songArtistCredits");
268       if (songArtistCreditsNode)
269         song.artistCredits.clear();
270       
271       while (songArtistCreditsNode)
272       {
273         if (songArtistCreditsNode->FirstChild())
274         {
275           CArtistCredit artistCredit;
276           XMLUtils::GetString(songArtistCreditsNode,  "artist",               artistCredit.m_strArtist);
277           XMLUtils::GetString(songArtistCreditsNode,  "musicBrainzArtistID",  artistCredit.m_strMusicBrainzArtistID);
278           XMLUtils::GetString(songArtistCreditsNode,  "joinphrase",           artistCredit.m_strJoinPhrase);
279           XMLUtils::GetBoolean(songArtistCreditsNode, "featuring",            artistCredit.m_boolFeatured);
280           song.artistCredits.push_back(artistCredit);
281         }
282         
283         songArtistCreditsNode = songArtistCreditsNode->NextSiblingElement("songArtistCredits");
284       }
285
286       XMLUtils::GetString(node,   "musicBrainzTrackID",   song.strMusicBrainzTrackID);
287       XMLUtils::GetInt(node, "position", song.iTrack);
288
289       if (song.iTrack == 0)
290         bIncrement = true;
291
292       XMLUtils::GetString(node,"title",song.strTitle);
293       CStdString strDur;
294       XMLUtils::GetString(node,"duration",strDur);
295       song.iDuration = StringUtils::TimeStringToSeconds(strDur);
296
297       if (bIncrement)
298         song.iTrack = song.iTrack + 1;
299
300       infoSongs.push_back(song);
301     }
302     node = node->NextSiblingElement("track");
303   }
304
305   return true;
306 }
307
308 bool CAlbum::Save(TiXmlNode *node, const CStdString &tag, const CStdString& strPath)
309 {
310   if (!node) return false;
311
312   // we start with a <tag> tag
313   TiXmlElement albumElement(tag.c_str());
314   TiXmlNode *album = node->InsertEndChild(albumElement);
315
316   if (!album) return false;
317
318   XMLUtils::SetString(album,                    "title", strAlbum);
319   XMLUtils::SetString(album,       "musicBrainzAlbumID", strMusicBrainzAlbumID);
320   XMLUtils::SetStringArray(album,              "artist", artist);
321   XMLUtils::SetStringArray(album,               "genre", genre);
322   XMLUtils::SetStringArray(album,               "style", styles);
323   XMLUtils::SetStringArray(album,                "mood", moods);
324   XMLUtils::SetStringArray(album,               "theme", themes);
325   XMLUtils::SetBoolean(album,      "compilation", bCompilation);
326
327   XMLUtils::SetString(album,      "review", strReview);
328   XMLUtils::SetString(album,        "type", strType);
329   XMLUtils::SetString(album, "releasedate", m_strDateOfRelease);
330   XMLUtils::SetString(album,       "label", strLabel);
331   XMLUtils::SetString(album,        "type", strType);
332   if (!thumbURL.m_xml.empty())
333   {
334     CXBMCTinyXML doc;
335     doc.Parse(thumbURL.m_xml);
336     const TiXmlNode* thumb = doc.FirstChild("thumb");
337     while (thumb)
338     {
339       album->InsertEndChild(*thumb);
340       thumb = thumb->NextSibling("thumb");
341     }
342   }
343   XMLUtils::SetString(album,        "path", strPath);
344
345   XMLUtils::SetInt(album,         "rating", iRating);
346   XMLUtils::SetInt(album,           "year", iYear);
347
348   for( VECARTISTCREDITS::const_iterator artistCredit = artistCredits.begin();artistCredit != artistCredits.end();++artistCredit)
349   {
350     // add an <albumArtistCredits> tag
351     TiXmlElement albumArtistCreditsElement("albumArtistCredits");
352     TiXmlNode *albumArtistCreditsNode = album->InsertEndChild(albumArtistCreditsElement);
353     XMLUtils::SetString(albumArtistCreditsNode,               "artist", artistCredit->m_strArtist);
354     XMLUtils::SetString(albumArtistCreditsNode,  "musicBrainzArtistID", artistCredit->m_strMusicBrainzArtistID);
355     XMLUtils::SetString(albumArtistCreditsNode,           "joinphrase", artistCredit->m_strJoinPhrase);
356     XMLUtils::SetString(albumArtistCreditsNode,            "featuring", artistCredit->GetArtist());
357   }
358
359   for( VECSONGS::const_iterator song = infoSongs.begin(); song != infoSongs.end(); ++song)
360   {
361     // add a <song> tag
362     TiXmlElement cast("track");
363     TiXmlNode *node = album->InsertEndChild(cast);
364     for( VECARTISTCREDITS::const_iterator artistCredit = song->artistCredits.begin(); artistCredit != song->artistCredits.end(); ++artistCredit)
365     {
366       // add an <albumArtistCredits> tag
367       TiXmlElement songArtistCreditsElement("songArtistCredits");
368       TiXmlNode *songArtistCreditsNode = node->InsertEndChild(songArtistCreditsElement);
369       XMLUtils::SetString(songArtistCreditsNode,               "artist", artistCredit->m_strArtist);
370       XMLUtils::SetString(songArtistCreditsNode,  "musicBrainzArtistID", artistCredit->m_strMusicBrainzArtistID);
371       XMLUtils::SetString(songArtistCreditsNode,           "joinphrase", artistCredit->m_strJoinPhrase);
372       XMLUtils::SetString(songArtistCreditsNode,            "featuring", artistCredit->GetArtist());
373     }
374     XMLUtils::SetString(node,   "musicBrainzTrackID",   song->strMusicBrainzTrackID);
375     XMLUtils::SetString(node,   "title",                song->strTitle);
376     XMLUtils::SetInt(node,      "position",             song->iTrack);
377     XMLUtils::SetString(node,   "duration",             StringUtils::SecondsToTimeString(song->iDuration));
378   }
379
380   return true;
381 }
382