Merge pull request #25808 from CastagnaIT/fix_url_parse
[xbmc.git] / xbmc / music / Song.cpp
blobb096af7a810b26c85bfe1934c27e5c15a45222db
1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "Song.h"
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"
20 using namespace MUSIC_INFO;
22 CSong::CSong(CFileItem& item)
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();
32 // Determine artist credits from various tag arrays
33 SetArtistCredits(tag.GetArtist(), tag.GetMusicBrainzArtistHints(), tag.GetMusicBrainzArtistID());
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();
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.GetStartOffset());
70 iEndOffset = static_cast<int>(item.GetEndOffset());
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 songVideoURL = tag.GetSongVideoURL();
81 CSong::CSong()
83 Clear();
86 void CSong::SetArtistCredits(const std::vector<std::string>& names, const std::vector<std::string>& hints,
87 const std::vector<std::string>& mbids)
89 artistCredits.clear();
90 std::vector<std::string> artistHints = hints;
91 //Split the artist sort string to try and get sort names for individual artists
92 std::vector<std::string> artistSort = StringUtils::Split(
93 strArtistSort,
94 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
96 // Vector of possible separators in the order least likely to be part of artist name
97 static const std::vector<std::string> separators{
98 " feat. ", " ft. ", " Feat. ", " Ft. ", ";", ":", "|", "#", "/", " with ", "&"};
100 if (!mbids.empty())
101 { // Have musicbrainz artist info, so use it
103 // Establish tag consistency - do the number of musicbrainz ids and number of names in hints or artist match
104 if (mbids.size() != artistHints.size() && mbids.size() != names.size())
106 // Tags mismatch - report it and then try to fix
107 CLog::Log(LOGDEBUG, "Mismatch in song file tags: {} mbid {} names {} {}", (int)mbids.size(),
108 (int)names.size(), strTitle, strArtistDesc);
110 Most likely we have no hints and a single artist name like "Artist1 feat. Artist2"
111 or "Composer; Conductor, Orchestra, Soloist" or "Artist1/Artist2" where the
112 expected single item separator (default = space-slash-space) as not been used.
113 Ampersand (&), comma and slash (no spaces) are poor delimiters as could be in name
114 e.g. "AC/DC", "Earth, Wind & Fire", but here treat them as such in attempt to find artist names.
115 When there are hints but count not match mbid they could be poorly formatted using unexpected
116 separators so attempt to split them. Or we could have more hints or artist names than
117 musicbrainz id so ignore them but raise warning.
119 // Do hints exist yet mismatch
120 if (artistHints.size() > 0 &&
121 artistHints.size() != mbids.size())
123 if (names.size() == mbids.size())
124 // Artist name count matches, use that as hints
125 artistHints = names;
126 else if (artistHints.size() < mbids.size())
127 { // Try splitting the hints until have matching number
128 artistHints = StringUtils::SplitMulti(artistHints, separators, mbids.size());
130 else
131 // Extra hints, discard them.
132 artistHints.resize(mbids.size());
134 // Do hints not exist or still mismatch, try artists
135 if (artistHints.size() != mbids.size())
136 artistHints = names;
137 // Still mismatch, try splitting the hints (now artists) until have matching number
138 if (artistHints.size() < mbids.size())
140 artistHints = StringUtils::SplitMulti(artistHints, separators, mbids.size());
143 else
144 { // Either hints or artist names (or both) matches number of musicbrainz id
145 // If hints mismatch, use artists
146 if (artistHints.size() != mbids.size())
147 artistHints = names;
150 // Try to get number of artist sort names and musicbrainz ids to match. Split sort names
151 // further using multiple possible delimiters, over single separator applied in Tag loader
152 if (artistSort.size() != mbids.size())
153 artistSort = StringUtils::SplitMulti(artistSort, separators);
155 for (size_t i = 0; i < mbids.size(); i++)
157 std::string artistId = mbids[i];
158 std::string artistName;
160 We try and get the corresponding artist name from the hints list.
161 Having already attempted to make the number of hints match, if they
162 still don't then use musicbrainz id as the name and hope later on we
163 can update that entry.
165 if (i < artistHints.size())
166 artistName = artistHints[i];
167 else
168 artistName = artistId;
170 // Use artist sort name providing we have as many as we have mbid,
171 // otherwise something is wrong with them so ignore and leave blank
172 if (artistSort.size() == mbids.size())
173 artistCredits.emplace_back(StringUtils::Trim(artistName), StringUtils::Trim(artistSort[i]), artistId);
174 else
175 artistCredits.emplace_back(StringUtils::Trim(artistName), "", artistId);
178 else
179 { // No musicbrainz artist ids, so fill in directly
180 // Separate artist names further, if possible, and trim blank space.
181 std::vector<std::string> artists = names;
182 if (artistHints.size() > names.size())
183 // Make use of hints (ARTISTS tag), when present, to separate artist names
184 artists = artistHints;
185 else
186 // Split artist names further using multiple possible delimiters, over single separator applied in Tag loader
187 artists = StringUtils::SplitMulti(artists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicArtistSeparators);
189 if (artistSort.size() != artists.size())
190 // Split artist sort names further using multiple possible delimiters, over single separator applied in Tag loader
191 artistSort = StringUtils::SplitMulti(artistSort, separators);
193 for (size_t i = 0; i < artists.size(); i++)
195 artistCredits.emplace_back(StringUtils::Trim(artists[i]));
196 // Set artist sort name providing we have as many as we have artists,
197 // otherwise something is wrong with them so ignore rather than guess.
198 if (artistSort.size() == artists.size())
199 artistCredits.back().SetSortName(StringUtils::Trim(artistSort[i]));
205 void CSong::MergeScrapedSong(const CSong& source, bool override)
207 // Merge when MusicBrainz Track ID match (checked in CAlbum::MergeScrapedAlbum)
208 if ((override && !source.strTitle.empty()) || strTitle.empty())
209 strTitle = source.strTitle;
210 if ((override && source.iTrack != 0) || iTrack == 0)
211 iTrack = source.iTrack;
212 if (override)
214 artistCredits = source.artistCredits; // Replace artists and store mbid returned by scraper
215 strArtistDesc.clear(); // @todo: set artist display string e.g. "artist1 feat. artist2" when scraped
219 void CSong::Serialize(CVariant& value) const
221 value["filename"] = strFileName;
222 value["title"] = strTitle;
223 value["artist"] = GetArtist();
224 value["artistsort"] = GetArtistSort(); // a string for the song not vector of values for each artist
225 value["album"] = strAlbum;
226 value["albumartist"] = GetAlbumArtist();
227 value["genre"] = genre;
228 value["duration"] = iDuration;
229 value["track"] = iTrack;
230 value["year"] = atoi(strReleaseDate.c_str());;
231 value["musicbrainztrackid"] = strMusicBrainzTrackID;
232 value["comment"] = strComment;
233 value["mood"] = strMood;
234 value["rating"] = rating;
235 value["userrating"] = userrating;
236 value["votes"] = votes;
237 value["timesplayed"] = iTimesPlayed;
238 value["lastplayed"] = lastPlayed.IsValid() ? lastPlayed.GetAsDBDateTime() : "";
239 value["dateadded"] = dateAdded.IsValid() ? dateAdded.GetAsDBDateTime() : "";
240 value["albumid"] = idAlbum;
241 value["albumreleasedate"] = strReleaseDate;
242 value["bpm"] = iBPM;
243 value["bitrate"] = iBitRate;
244 value["samplerate"] = iSampleRate;
245 value["channels"] = iChannels;
246 value["songvideourl"] = songVideoURL;
249 void CSong::Clear()
251 strFileName.clear();
252 strTitle.clear();
253 strAlbum.clear();
254 strArtistSort.clear();
255 strArtistDesc.clear();
256 m_albumArtist.clear();
257 m_strAlbumArtistSort.clear();
258 genre.clear();
259 strThumb.clear();
260 strMusicBrainzTrackID.clear();
261 m_musicRoles.clear();
262 strComment.clear();
263 strMood.clear();
264 rating = 0;
265 userrating = 0;
266 votes = 0;
267 iTrack = 0;
268 iDuration = 0;
269 strOrigReleaseDate.clear();
270 strReleaseDate.clear();
271 strDiscSubtitle.clear();
272 iStartOffset = 0;
273 iEndOffset = 0;
274 idSong = -1;
275 iTimesPlayed = 0;
276 lastPlayed.Reset();
277 dateAdded.Reset();
278 dateUpdated.Reset();
279 dateNew.Reset();
280 idAlbum = -1;
281 bCompilation = false;
282 embeddedArt.Clear();
283 iBPM = 0;
284 iBitRate = 0;
285 iSampleRate = 0;
286 iChannels = 0;
287 songVideoURL.clear();
289 replayGain = ReplayGain();
291 const std::vector<std::string> CSong::GetArtist() const
293 //Get artist names as vector from artist credits
294 std::vector<std::string> songartists;
295 for (const auto& artistCredit : artistCredits)
297 songartists.push_back(artistCredit.GetArtist());
299 //When artist credits have not been populated attempt to build an artist vector from the description string
300 //This is a temporary fix, in the longer term other areas should query the song_artist table and populate
301 //artist credits. Note that splitting the string may not give the same artists as held in the song_artist table
302 if (songartists.empty() && !strArtistDesc.empty())
303 songartists = StringUtils::Split(strArtistDesc, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
304 return songartists;
307 const std::string CSong::GetArtistSort() const
309 //The stored artist sort name string takes precedence but a
310 //value could be created from individual sort names held in artistcredits
311 if (!strArtistSort.empty())
312 return strArtistSort;
313 std::vector<std::string> artistvector;
314 for (const auto& artistcredit : artistCredits)
315 if (!artistcredit.GetSortName().empty())
316 artistvector.emplace_back(artistcredit.GetSortName());
317 std::string artistString;
318 if (!artistvector.empty())
319 artistString = StringUtils::Join(artistvector, "; ");
320 return artistString;
323 const std::vector<std::string> CSong::GetMusicBrainzArtistID() const
325 //Get artist MusicBrainz IDs as vector from artist credits
326 std::vector<std::string> musicBrainzID;
327 for (const auto& artistCredit : artistCredits)
329 musicBrainzID.push_back(artistCredit.GetMusicBrainzArtistID());
331 return musicBrainzID;
334 const std::string CSong::GetArtistString() const
336 //Artist description may be different from the artists in artistcredits (see ARTISTS tag processing)
337 //but is takes precedence as a string because artistcredits is not always filled during processing
338 if (!strArtistDesc.empty())
339 return strArtistDesc;
340 std::vector<std::string> artistvector;
341 for (const auto& i : artistCredits)
342 artistvector.push_back(i.GetArtist());
343 std::string artistString;
344 if (!artistvector.empty())
345 artistString = StringUtils::Join(artistvector, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
346 return artistString;
349 const std::vector<int> CSong::GetArtistIDArray() const
351 // Get song artist IDs for json rpc
352 std::vector<int> artistids;
353 for (const auto& artistCredit : artistCredits)
354 artistids.push_back(artistCredit.GetArtistId());
355 return artistids;
358 void CSong::AppendArtistRole(const CMusicRole& musicRole)
360 m_musicRoles.push_back(musicRole);
363 bool CSong::HasArt() const
365 if (!strThumb.empty()) return true;
366 if (!embeddedArt.Empty()) return true;
367 return false;
370 bool CSong::ArtMatches(const CSong &right) const
372 return (right.strThumb == strThumb &&
373 embeddedArt.Matches(right.embeddedArt));