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 "Song.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/StringUtils.h"
17 #include "utils/Variant.h"
18 #include "utils/log.h"
19 
20 using namespace MUSIC_INFO;
21 
CSong(CFileItem & item)22 CSong::CSong(CFileItem& item)
23 {
24   CMusicInfoTag& tag = *item.GetMusicInfoTag();
25   strTitle = tag.GetTitle();
26   genre = tag.GetGenre();
27   strArtistDesc = tag.GetArtistString();
28   //Set sort string before processing artist credits
29   strArtistSort = tag.GetArtistSort();
30   m_strComposerSort = tag.GetComposerSort();
31 
32   // Determine artist credits from various tag arrays
33   SetArtistCredits(tag.GetArtist(), tag.GetMusicBrainzArtistHints(), tag.GetMusicBrainzArtistID());
34 
35   strAlbum = tag.GetAlbum();
36   m_albumArtist = tag.GetAlbumArtist();
37   // Separate album artist names further, if possible, and trim blank space.
38   if (tag.GetMusicBrainzAlbumArtistHints().size() > m_albumArtist.size())
39     // Make use of hints (ALBUMARTISTS tag), when present, to separate artist names
40     m_albumArtist = tag.GetMusicBrainzAlbumArtistHints();
41   else
42     // Split album artist names further using multiple possible delimiters, over single separator applied in Tag loader
43     m_albumArtist = StringUtils::SplitMulti(m_albumArtist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicArtistSeparators);
44   for (auto artistname : m_albumArtist)
45     StringUtils::Trim(artistname);
46   m_strAlbumArtistSort = tag.GetAlbumArtistSort();
47 
48   strMusicBrainzTrackID = tag.GetMusicBrainzTrackID();
49   m_musicRoles = tag.GetContributors();
50   strComment = tag.GetComment();
51   strCueSheet = tag.GetCueSheet();
52   strMood = tag.GetMood();
53   rating = tag.GetRating();
54   userrating = tag.GetUserrating();
55   votes = tag.GetVotes();
56   strOrigReleaseDate = tag.GetOriginalDate();
57   strReleaseDate = tag.GetReleaseDate();
58   strDiscSubtitle = tag.GetDiscSubtitle();
59   iTrack = tag.GetTrackAndDiscNumber();
60   iDuration = tag.GetDuration();
61   strRecordLabel = tag.GetRecordLabel();
62   strAlbumType = tag.GetMusicBrainzReleaseType();
63   bCompilation = tag.GetCompilation();
64   embeddedArt = tag.GetCoverArtInfo();
65   strFileName = tag.GetURL().empty() ? item.GetPath() : tag.GetURL();
66   dateAdded = tag.GetDateAdded();
67   replayGain = tag.GetReplayGain();
68   strThumb = item.GetUserMusicThumb(true);
69   iStartOffset = static_cast<int>(item.m_lStartOffset);
70   iEndOffset = static_cast<int>(item.m_lEndOffset);
71   idSong = -1;
72   iTimesPlayed = 0;
73   idAlbum = -1;
74   iBPM = tag.GetBPM();
75   iSampleRate = tag.GetSampleRate();
76   iBitRate = tag.GetBitRate();
77   iChannels = tag.GetNoOfChannels();
78 }
79 
CSong()80 CSong::CSong()
81 {
82   Clear();
83 }
84 
SetArtistCredits(const std::vector<std::string> & names,const std::vector<std::string> & hints,const std::vector<std::string> & mbids)85 void CSong::SetArtistCredits(const std::vector<std::string>& names, const std::vector<std::string>& hints,
86   const std::vector<std::string>& mbids)
87 {
88   artistCredits.clear();
89   std::vector<std::string> artistHints = hints;
90   //Split the artist sort string to try and get sort names for individual artists
91   std::vector<std::string> artistSort = StringUtils::Split(strArtistSort, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
92 
93   if (!mbids.empty())
94   { // Have musicbrainz artist info, so use it
95 
96     // Vector of possible separators in the order least likely to be part of artist name
97     const std::vector<std::string> separators{ " feat. ", " ft. ", " Feat. "," Ft. ", ";", ":", "|", "#", "/", " with ", ",", "&" };
98 
99     // Establish tag consistency - do the number of musicbrainz ids and number of names in hints or artist match
100     if (mbids.size() != artistHints.size() && mbids.size() != names.size())
101     {
102       // Tags mis-match - report it and then try to fix
103       CLog::Log(LOGDEBUG, "Mis-match in song file tags: %i mbid %i names %s %s",
104         (int)mbids.size(), (int)names.size(), strTitle.c_str(), strArtistDesc.c_str());
105       /*
106         Most likely we have no hints and a single artist name like "Artist1 feat. Artist2"
107         or "Composer; Conductor, Orchestra, Soloist" or "Artist1/Artist2" where the
108         expected single item separator (default = space-slash-space) as not been used.
109         Ampersand (&), comma and slash (no spaces) are poor delimiters as could be in name
110         e.g. "AC/DC", "Earth, Wind & Fire", but here treat them as such in attempt to find artist names.
111         When there are hints but count not match mbid they could be poorly formatted using unexpected
112         separators so attempt to split them. Or we could have more hints or artist names than
113         musicbrainz id so ignore them but raise warning.
114       */
115       // Do hints exist yet mis-match
116       if (artistHints.size() > 0 &&
117         artistHints.size() != mbids.size())
118       {
119         if (names.size() == mbids.size())
120           // Artist name count matches, use that as hints
121           artistHints = names;
122         else if (artistHints.size() < mbids.size())
123         { // Try splitting the hints until have matching number
124           artistHints = StringUtils::SplitMulti(artistHints, separators, mbids.size());
125         }
126         else
127           // Extra hints, discard them.
128           artistHints.resize(mbids.size());
129       }
130       // Do hints not exist or still mis-match, try artists
131       if (artistHints.size() != mbids.size())
132         artistHints = names;
133       // Still mis-match, try splitting the hints (now artists) until have matching number
134       if (artistHints.size() < mbids.size())
135       {
136         artistHints = StringUtils::SplitMulti(artistHints, separators, mbids.size());
137       }
138     }
139     else
140     { // Either hints or artist names (or both) matches number of musicbrainz id
141       // If hints mis-match, use artists
142       if (artistHints.size() != mbids.size())
143         artistHints = names;
144     }
145 
146     // Try to get number of artist sort names and musicbrainz ids to match. Split sort names
147     // further using multiple possible delimiters, over single separator applied in Tag loader
148     if (artistSort.size() != mbids.size())
149       artistSort = StringUtils::SplitMulti(artistSort, { ";", ":", "|", "#" });
150 
151     for (size_t i = 0; i < mbids.size(); i++)
152     {
153       std::string artistId = mbids[i];
154       std::string artistName;
155       /*
156        We try and get the corresponding artist name from the hints list.
157        Having already attempted to make the number of hints match, if they
158        still don't then use musicbrainz id as the name and hope later on we
159        can update that entry.
160       */
161       if (i < artistHints.size())
162         artistName = artistHints[i];
163       else
164         artistName = artistId;
165 
166       // Use artist sort name providing we have as many as we have mbid,
167       // otherwise something is wrong with them so ignore and leave blank
168       if (artistSort.size() == mbids.size())
169         artistCredits.emplace_back(StringUtils::Trim(artistName), StringUtils::Trim(artistSort[i]), artistId);
170       else
171         artistCredits.emplace_back(StringUtils::Trim(artistName), "", artistId);
172     }
173   }
174   else
175   { // No musicbrainz artist ids, so fill in directly
176     // Separate artist names further, if possible, and trim blank space.
177     std::vector<std::string> artists = names;
178     if (artistHints.size() > names.size())
179       // Make use of hints (ARTISTS tag), when present, to separate artist names
180       artists = artistHints;
181     else
182       // Split artist names further using multiple possible delimiters, over single separator applied in Tag loader
183       artists = StringUtils::SplitMulti(artists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicArtistSeparators);
184 
185     if (artistSort.size() != artists.size())
186       // Split artist sort names further using multiple possible delimiters, over single separator applied in Tag loader
187       artistSort = StringUtils::SplitMulti(artistSort, { ";", ":", "|", "#" });
188 
189     for (size_t i = 0; i < artists.size(); i++)
190     {
191       artistCredits.emplace_back(StringUtils::Trim(artists[i]));
192       // Set artist sort name providing we have as many as we have artists,
193       // otherwise something is wrong with them so ignore rather than guess.
194       if (artistSort.size() == artists.size())
195         artistCredits.back().SetSortName(StringUtils::Trim(artistSort[i]));
196     }
197   }
198 
199 }
200 
MergeScrapedSong(const CSong & source,bool override)201 void CSong::MergeScrapedSong(const CSong& source, bool override)
202 {
203   // Merge when MusicBrainz Track ID match (checked in CAlbum::MergeScrapedAlbum)
204   if ((override && !source.strTitle.empty()) || strTitle.empty())
205     strTitle = source.strTitle;
206   if ((override && source.iTrack != 0) || iTrack == 0)
207     iTrack = source.iTrack;
208   if (override)
209   {
210     artistCredits = source.artistCredits; // Replace artists and store mbid returned by scraper
211     strArtistDesc.clear();  // @todo: set artist display string e.g. "artist1 feat. artist2" when scraped
212   }
213 }
214 
Serialize(CVariant & value) const215 void CSong::Serialize(CVariant& value) const
216 {
217   value["filename"] = strFileName;
218   value["title"] = strTitle;
219   value["artist"] = GetArtist();
220   value["artistsort"] = GetArtistSort();  // a string for the song not vector of values for each artist
221   value["album"] = strAlbum;
222   value["albumartist"] = GetAlbumArtist();
223   value["genre"] = genre;
224   value["duration"] = iDuration;
225   value["track"] = iTrack;
226   value["year"] = atoi(strReleaseDate.c_str());;
227   value["musicbrainztrackid"] = strMusicBrainzTrackID;
228   value["comment"] = strComment;
229   value["mood"] = strMood;
230   value["rating"] = rating;
231   value["userrating"] = userrating;
232   value["votes"] = votes;
233   value["timesplayed"] = iTimesPlayed;
234   value["lastplayed"] = lastPlayed.IsValid() ? lastPlayed.GetAsDBDateTime() : "";
235   value["dateadded"] = dateAdded.IsValid() ? dateAdded.GetAsDBDateTime() : "";
236   value["albumid"] = idAlbum;
237   value["albumreleasedate"] = strReleaseDate;
238   value["bpm"] = iBPM;
239   value["bitrate"] = iBitRate;
240   value["samplerate"] = iSampleRate;
241   value["channels"] = iChannels;
242 }
243 
Clear()244 void CSong::Clear()
245 {
246   strFileName.clear();
247   strTitle.clear();
248   strAlbum.clear();
249   strArtistSort.clear();
250   strArtistDesc.clear();
251   m_albumArtist.clear();
252   m_strAlbumArtistSort.clear();
253   genre.clear();
254   strThumb.clear();
255   strMusicBrainzTrackID.clear();
256   m_musicRoles.clear();
257   strComment.clear();
258   strMood.clear();
259   rating = 0;
260   userrating = 0;
261   votes = 0;
262   iTrack = 0;
263   iDuration = 0;
264   strOrigReleaseDate.clear();
265   strReleaseDate.clear();
266   strDiscSubtitle.clear();
267   iStartOffset = 0;
268   iEndOffset = 0;
269   idSong = -1;
270   iTimesPlayed = 0;
271   lastPlayed.Reset();
272   dateAdded.Reset();
273   dateUpdated.Reset();
274   dateNew.Reset();
275   idAlbum = -1;
276   bCompilation = false;
277   embeddedArt.Clear();
278   iBPM = 0;
279   iBitRate = 0;
280   iSampleRate = 0;
281   iChannels =  0;
282 
283   replayGain = ReplayGain();
284 }
GetArtist() const285 const std::vector<std::string> CSong::GetArtist() const
286 {
287   //Get artist names as vector from artist credits
288   std::vector<std::string> songartists;
289   for (const auto& artistCredit : artistCredits)
290   {
291     songartists.push_back(artistCredit.GetArtist());
292   }
293   //When artist credits have not been populated attempt to build an artist vector from the description string
294   //This is a temporary fix, in the longer term other areas should query the song_artist table and populate
295   //artist credits. Note that splitting the string may not give the same artists as held in the song_artist table
296   if (songartists.empty() && !strArtistDesc.empty())
297     songartists = StringUtils::Split(strArtistDesc, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
298   return songartists;
299 }
300 
GetArtistSort() const301 const std::string CSong::GetArtistSort() const
302 {
303   //The stored artist sort name string takes precidence but a
304   //value could be created from individual sort names held in artistcredits
305   if (!strArtistSort.empty())
306     return strArtistSort;
307   std::vector<std::string> artistvector;
308   for (const auto& artistcredit : artistCredits)
309     if (!artistcredit.GetSortName().empty())
310       artistvector.emplace_back(artistcredit.GetSortName());
311   std::string artistString;
312   if (!artistvector.empty())
313     artistString = StringUtils::Join(artistvector, "; ");
314   return artistString;
315 }
316 
GetMusicBrainzArtistID() const317 const std::vector<std::string> CSong::GetMusicBrainzArtistID() const
318 {
319   //Get artist MusicBrainz IDs as vector from artist credits
320   std::vector<std::string> musicBrainzID;
321   for (const auto& artistCredit : artistCredits)
322   {
323     musicBrainzID.push_back(artistCredit.GetMusicBrainzArtistID());
324   }
325   return musicBrainzID;
326 }
327 
GetArtistString() const328 const std::string CSong::GetArtistString() const
329 {
330   //Artist description may be different from the artists in artistcredits (see ARTISTS tag processing)
331   //but is takes precedence as a string because artistcredits is not always filled during processing
332   if (!strArtistDesc.empty())
333     return strArtistDesc;
334   std::vector<std::string> artistvector;
335   for (const auto& i : artistCredits)
336     artistvector.push_back(i.GetArtist());
337   std::string artistString;
338   if (!artistvector.empty())
339     artistString = StringUtils::Join(artistvector, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
340   return artistString;
341 }
342 
GetArtistIDArray() const343 const std::vector<int> CSong::GetArtistIDArray() const
344 {
345   // Get song artist IDs for json rpc
346   std::vector<int> artistids;
347   for (const auto& artistCredit : artistCredits)
348     artistids.push_back(artistCredit.GetArtistId());
349   return artistids;
350 }
351 
AppendArtistRole(const CMusicRole & musicRole)352 void CSong::AppendArtistRole(const CMusicRole& musicRole)
353 {
354   m_musicRoles.push_back(musicRole);
355 }
356 
HasArt() const357 bool CSong::HasArt() const
358 {
359   if (!strThumb.empty()) return true;
360   if (!embeddedArt.Empty()) return true;
361   return false;
362 }
363 
ArtMatches(const CSong & right) const364 bool CSong::ArtMatches(const CSong &right) const
365 {
366   return (right.strThumb == strThumb &&
367           embeddedArt.Matches(right.embeddedArt));
368 }
369 
GetDiscSubtitle() const370 const std::string CSong::GetDiscSubtitle() const
371 {
372   return strDiscSubtitle;
373 }
374