[release] version bump to 13.0 beta1
[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   bool hasMusicBrainzAlbumArtist = !tag.GetMusicBrainzAlbumArtistID().empty();
42   const vector<string>& artists = hasMusicBrainzAlbumArtist ? tag.GetMusicBrainzAlbumArtistID() : tag.GetAlbumArtist();
43   for (vector<string>::const_iterator it = artists.begin(); it != artists.end(); ++it)
44   {
45     CStdString artistName = hasMusicBrainzAlbumArtist && !artist.empty() ? artist[0] : *it;
46     CStdString artistId = hasMusicBrainzAlbumArtist ? *it : StringUtils::EmptyString;
47     CStdString strJoinPhrase = (it == --artists.end() ? "" : g_advancedSettings.m_musicItemSeparator);
48     CArtistCredit artistCredit(artistName, artistId, strJoinPhrase);
49     artistCredits.push_back(artistCredit);
50   }
51   iYear = stTime.wYear;
52   bCompilation = tag.GetCompilation();
53   iTimesPlayed = 0;
54 }
55
56 void CAlbum::MergeScrapedAlbum(const CAlbum& source, bool override /* = true */)
57 {
58   /*
59    We don't merge musicbrainz album ID so that a refresh of album information
60    allows a lookup based on name rather than directly (re)using musicbrainz.
61    In future, we may wish to be able to override lookup by musicbrainz so
62    this might be dropped.
63    */
64 //  strMusicBrainzAlbumID = source.strMusicBrainzAlbumID;
65   if ((override && !source.genre.empty()) || genre.empty())
66     genre = source.genre;
67   if ((override && !source.strAlbum.empty()) || strAlbum.empty())
68     strAlbum = source.strAlbum;
69   if ((override && source.iYear > 0) || iYear == 0)
70     iYear = source.iYear;
71   if (override)
72     bCompilation = source.bCompilation;
73   //  iTimesPlayed = source.iTimesPlayed; // times played is derived from songs
74   for (std::map<std::string, std::string>::const_iterator i = source.art.begin(); i != source.art.end(); ++i)
75   {
76     if (override || art.find(i->first) == art.end())
77       art[i->first] = i->second;
78   }
79   strLabel = source.strLabel;
80   thumbURL = source.thumbURL;
81   moods = source.moods;
82   styles = source.styles;
83   themes = source.themes;
84   strReview = source.strReview;
85   strType = source.strType;
86 //  strPath = source.strPath; // don't merge the path
87   m_strDateOfRelease = source.m_strDateOfRelease;
88   iRating = source.iRating;
89   if (override)
90   {
91     artistCredits = source.artistCredits;
92     artist = source.artist; // artist information is read-only from the database. artistCredits is what counts on scan
93   }
94   else if (source.artistCredits.size() > artistCredits.size())
95     artistCredits.insert(artistCredits.end(), source.artistCredits.begin()+artistCredits.size(), source.artistCredits.end());
96   if (!strMusicBrainzAlbumID.empty())
97   {
98     /* update local songs with MB information */
99     for (VECSONGS::iterator song = songs.begin(); song != songs.end(); ++song)
100     {
101       if (!song->strMusicBrainzTrackID.empty())
102         for (VECSONGS::const_iterator sourceSong = source.infoSongs.begin(); sourceSong != source.infoSongs.end(); ++sourceSong)
103           if (sourceSong->strMusicBrainzTrackID == song->strMusicBrainzTrackID)
104             song->MergeScrapedSong(*sourceSong, override);
105     }
106   }
107   infoSongs = source.infoSongs;
108 }
109
110 CStdString CAlbum::GetArtistString() const
111 {
112   return StringUtils::Join(artist, g_advancedSettings.m_musicItemSeparator);
113 }
114
115 CStdString CAlbum::GetGenreString() const
116 {
117   return StringUtils::Join(genre, g_advancedSettings.m_musicItemSeparator);
118 }
119
120 bool CAlbum::operator<(const CAlbum &a) const
121 {
122   if (strMusicBrainzAlbumID.empty() && a.strMusicBrainzAlbumID.empty())
123   {
124     if (strAlbum < a.strAlbum) return true;
125     if (strAlbum > a.strAlbum) return false;
126
127     // This will do an std::vector compare (i.e. item by item)
128     if (artist < a.artist) return true;
129     if (artist > a.artist) return false;
130     return false;
131   }
132
133   if (strMusicBrainzAlbumID < a.strMusicBrainzAlbumID) return true;
134   if (strMusicBrainzAlbumID > a.strMusicBrainzAlbumID) return false;
135   return false;
136 }
137
138 bool CAlbum::Load(const TiXmlElement *album, bool append, bool prioritise)
139 {
140   if (!album) return false;
141   if (!append)
142     Reset();
143
144   XMLUtils::GetString(album,              "title", strAlbum);
145   XMLUtils::GetString(album, "musicBrainzAlbumID", strMusicBrainzAlbumID);
146
147   XMLUtils::GetStringArray(album, "artist", artist, prioritise, g_advancedSettings.m_musicItemSeparator);
148   XMLUtils::GetStringArray(album, "genre", genre, prioritise, g_advancedSettings.m_musicItemSeparator);
149   XMLUtils::GetStringArray(album, "style", styles, prioritise, g_advancedSettings.m_musicItemSeparator);
150   XMLUtils::GetStringArray(album, "mood", moods, prioritise, g_advancedSettings.m_musicItemSeparator);
151   XMLUtils::GetStringArray(album, "theme", themes, prioritise, g_advancedSettings.m_musicItemSeparator);
152   XMLUtils::GetBoolean(album, "compilation", bCompilation);
153
154   XMLUtils::GetString(album,"review",strReview);
155   XMLUtils::GetString(album,"releasedate",m_strDateOfRelease);
156   XMLUtils::GetString(album,"label",strLabel);
157   XMLUtils::GetString(album,"type",strType);
158
159   XMLUtils::GetInt(album,"year",iYear);
160   const TiXmlElement* rElement = album->FirstChildElement("rating");
161   if (rElement)
162   {
163     float rating = 0;
164     float max_rating = 5;
165     XMLUtils::GetFloat(album, "rating", rating);
166     if (rElement->QueryFloatAttribute("max", &max_rating) == TIXML_SUCCESS && max_rating>=1)
167       rating *= (5.f / max_rating); // Normalise the Rating to between 0 and 5 
168     if (rating > 5.f)
169       rating = 5.f;
170     iRating = MathUtils::round_int(rating);
171   }
172
173   size_t iThumbCount = thumbURL.m_url.size();
174   CStdString xmlAdd = thumbURL.m_xml;
175   const TiXmlElement* thumb = album->FirstChildElement("thumb");
176   while (thumb)
177   {
178     thumbURL.ParseElement(thumb);
179     if (prioritise)
180     {
181       CStdString temp;
182       temp << *thumb;
183       xmlAdd = temp+xmlAdd;
184     }
185     thumb = thumb->NextSiblingElement("thumb");
186   }
187   // prioritise thumbs from nfos
188   if (prioritise && iThumbCount && iThumbCount != thumbURL.m_url.size())
189   {
190     rotate(thumbURL.m_url.begin(),
191            thumbURL.m_url.begin()+iThumbCount, 
192            thumbURL.m_url.end());
193     thumbURL.m_xml = xmlAdd;
194   }
195
196   const TiXmlElement* albumArtistCreditsNode = album->FirstChildElement("albumArtistCredits");
197   if (albumArtistCreditsNode)
198     artistCredits.clear();
199
200   while (albumArtistCreditsNode)
201   {
202     if (albumArtistCreditsNode->FirstChild())
203     {
204       CArtistCredit artistCredit;
205       XMLUtils::GetString(albumArtistCreditsNode,  "artist",               artistCredit.m_strArtist);
206       XMLUtils::GetString(albumArtistCreditsNode,  "musicBrainzArtistID",  artistCredit.m_strMusicBrainzArtistID);
207       XMLUtils::GetString(albumArtistCreditsNode,  "joinphrase",           artistCredit.m_strJoinPhrase);
208       XMLUtils::GetBoolean(albumArtistCreditsNode, "featuring",            artistCredit.m_boolFeatured);
209       artistCredits.push_back(artistCredit);
210     }
211
212     albumArtistCreditsNode = albumArtistCreditsNode->NextSiblingElement("albumArtistCredits");
213   }
214
215   // Support old style <artist></artist> for backwards compatibility
216   // .nfo files should ideally be updated to use the artist credits structure above
217   // or removed entirely in preference for better tags (MusicBrainz?)
218   if (artistCredits.empty() && !artist.empty())
219   {
220     for (vector<string>::const_iterator it = artist.begin(); it != artist.end(); ++it)
221     {
222       CArtistCredit artistCredit(*it, StringUtils::EmptyString,
223                                  it == --artist.end() ? StringUtils::EmptyString : g_advancedSettings.m_musicItemSeparator);
224       artistCredits.push_back(artistCredit);
225     }
226   }
227
228   const TiXmlElement* node = album->FirstChildElement("track");
229   if (node)
230     infoSongs.clear();  // this means that the tracks can't be spread over separate pages
231                     // but this is probably a reasonable limitation
232   bool bIncrement = false;
233   while (node)
234   {
235     if (node->FirstChild())
236     {
237
238       CSong song;
239       const TiXmlElement* songArtistCreditsNode = node->FirstChildElement("songArtistCredits");
240       if (songArtistCreditsNode)
241         song.artistCredits.clear();
242       
243       while (songArtistCreditsNode)
244       {
245         if (songArtistCreditsNode->FirstChild())
246         {
247           CArtistCredit artistCredit;
248           XMLUtils::GetString(songArtistCreditsNode,  "artist",               artistCredit.m_strArtist);
249           XMLUtils::GetString(songArtistCreditsNode,  "musicBrainzArtistID",  artistCredit.m_strMusicBrainzArtistID);
250           XMLUtils::GetString(songArtistCreditsNode,  "joinphrase",           artistCredit.m_strJoinPhrase);
251           XMLUtils::GetBoolean(songArtistCreditsNode, "featuring",            artistCredit.m_boolFeatured);
252           song.artistCredits.push_back(artistCredit);
253         }
254         
255         songArtistCreditsNode = songArtistCreditsNode->NextSiblingElement("songArtistCredits");
256       }
257
258       XMLUtils::GetString(node,   "musicBrainzTrackID",   song.strMusicBrainzTrackID);
259       XMLUtils::GetInt(node, "position", song.iTrack);
260
261       if (song.iTrack == 0)
262         bIncrement = true;
263
264       XMLUtils::GetString(node,"title",song.strTitle);
265       CStdString strDur;
266       XMLUtils::GetString(node,"duration",strDur);
267       song.iDuration = StringUtils::TimeStringToSeconds(strDur);
268
269       if (bIncrement)
270         song.iTrack = song.iTrack + 1;
271
272       infoSongs.push_back(song);
273     }
274     node = node->NextSiblingElement("track");
275   }
276
277   return true;
278 }
279
280 bool CAlbum::Save(TiXmlNode *node, const CStdString &tag, const CStdString& strPath)
281 {
282   if (!node) return false;
283
284   // we start with a <tag> tag
285   TiXmlElement albumElement(tag.c_str());
286   TiXmlNode *album = node->InsertEndChild(albumElement);
287
288   if (!album) return false;
289
290   XMLUtils::SetString(album,                    "title", strAlbum);
291   XMLUtils::SetString(album,       "musicBrainzAlbumID", strMusicBrainzAlbumID);
292   XMLUtils::SetStringArray(album,              "artist", artist);
293   XMLUtils::SetStringArray(album,               "genre", genre);
294   XMLUtils::SetStringArray(album,               "style", styles);
295   XMLUtils::SetStringArray(album,                "mood", moods);
296   XMLUtils::SetStringArray(album,               "theme", themes);
297   XMLUtils::SetBoolean(album,      "compilation", bCompilation);
298
299   XMLUtils::SetString(album,      "review", strReview);
300   XMLUtils::SetString(album,        "type", strType);
301   XMLUtils::SetString(album, "releasedate", m_strDateOfRelease);
302   XMLUtils::SetString(album,       "label", strLabel);
303   XMLUtils::SetString(album,        "type", strType);
304   if (!thumbURL.m_xml.empty())
305   {
306     CXBMCTinyXML doc;
307     doc.Parse(thumbURL.m_xml);
308     const TiXmlNode* thumb = doc.FirstChild("thumb");
309     while (thumb)
310     {
311       album->InsertEndChild(*thumb);
312       thumb = thumb->NextSibling("thumb");
313     }
314   }
315   XMLUtils::SetString(album,        "path", strPath);
316
317   XMLUtils::SetInt(album,         "rating", iRating);
318   XMLUtils::SetInt(album,           "year", iYear);
319
320   for( VECARTISTCREDITS::const_iterator artistCredit = artistCredits.begin();artistCredit != artistCredits.end();++artistCredit)
321   {
322     // add an <albumArtistCredits> tag
323     TiXmlElement albumArtistCreditsElement("albumArtistCredits");
324     TiXmlNode *albumArtistCreditsNode = album->InsertEndChild(albumArtistCreditsElement);
325     XMLUtils::SetString(albumArtistCreditsNode,               "artist", artistCredit->m_strArtist);
326     XMLUtils::SetString(albumArtistCreditsNode,  "musicBrainzArtistID", artistCredit->m_strMusicBrainzArtistID);
327     XMLUtils::SetString(albumArtistCreditsNode,           "joinphrase", artistCredit->m_strJoinPhrase);
328     XMLUtils::SetString(albumArtistCreditsNode,            "featuring", artistCredit->GetArtist());
329   }
330
331   for( VECSONGS::const_iterator song = infoSongs.begin(); song != infoSongs.end(); ++song)
332   {
333     // add a <song> tag
334     TiXmlElement cast("track");
335     TiXmlNode *node = album->InsertEndChild(cast);
336     for( VECARTISTCREDITS::const_iterator artistCredit = song->artistCredits.begin(); artistCredit != song->artistCredits.end(); ++artistCredit)
337     {
338       // add an <albumArtistCredits> tag
339       TiXmlElement songArtistCreditsElement("songArtistCredits");
340       TiXmlNode *songArtistCreditsNode = node->InsertEndChild(songArtistCreditsElement);
341       XMLUtils::SetString(songArtistCreditsNode,               "artist", artistCredit->m_strArtist);
342       XMLUtils::SetString(songArtistCreditsNode,  "musicBrainzArtistID", artistCredit->m_strMusicBrainzArtistID);
343       XMLUtils::SetString(songArtistCreditsNode,           "joinphrase", artistCredit->m_strJoinPhrase);
344       XMLUtils::SetString(songArtistCreditsNode,            "featuring", artistCredit->GetArtist());
345     }
346     XMLUtils::SetString(node,   "musicBrainzTrackID",   song->strMusicBrainzTrackID);
347     XMLUtils::SetString(node,   "title",                song->strTitle);
348     XMLUtils::SetInt(node,      "position",             song->iTrack);
349     XMLUtils::SetString(node,   "duration",             StringUtils::SecondsToTimeString(song->iDuration));
350   }
351
352   return true;
353 }
354