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