[FileItem][Cleanup] Add getter/setter for start/end offset
[xbmc.git] / xbmc / music / Song.cpp
blob165889ca924445e733b3454f0b760f99ee2058fc
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();
80 CSong::CSong()
82 Clear();
85 void CSong::SetArtistCredits(const std::vector<std::string>& names, const std::vector<std::string>& hints,
86 const std::vector<std::string>& mbids)
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);
93 if (!mbids.empty())
94 { // Have musicbrainz artist info, so use it
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 ", ",", "&" };
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())
102 // Tags mismatch - report it and then try to fix
103 CLog::Log(LOGDEBUG, "Mismatch in song file tags: {} mbid {} names {} {}", (int)mbids.size(),
104 (int)names.size(), strTitle, strArtistDesc);
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.
115 // Do hints exist yet mismatch
116 if (artistHints.size() > 0 &&
117 artistHints.size() != mbids.size())
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());
126 else
127 // Extra hints, discard them.
128 artistHints.resize(mbids.size());
130 // Do hints not exist or still mismatch, try artists
131 if (artistHints.size() != mbids.size())
132 artistHints = names;
133 // Still mismatch, try splitting the hints (now artists) until have matching number
134 if (artistHints.size() < mbids.size())
136 artistHints = StringUtils::SplitMulti(artistHints, separators, mbids.size());
139 else
140 { // Either hints or artist names (or both) matches number of musicbrainz id
141 // If hints mismatch, use artists
142 if (artistHints.size() != mbids.size())
143 artistHints = names;
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, { ";", ":", "|", "#" });
151 for (size_t i = 0; i < mbids.size(); i++)
153 std::string artistId = mbids[i];
154 std::string artistName;
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.
161 if (i < artistHints.size())
162 artistName = artistHints[i];
163 else
164 artistName = artistId;
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);
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);
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, { ";", ":", "|", "#" });
189 for (size_t i = 0; i < artists.size(); i++)
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]));
201 void CSong::MergeScrapedSong(const CSong& source, bool override)
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)
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
215 void CSong::Serialize(CVariant& value) const
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;
244 void CSong::Clear()
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;
283 replayGain = ReplayGain();
285 const std::vector<std::string> CSong::GetArtist() const
287 //Get artist names as vector from artist credits
288 std::vector<std::string> songartists;
289 for (const auto& artistCredit : artistCredits)
291 songartists.push_back(artistCredit.GetArtist());
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;
301 const std::string CSong::GetArtistSort() const
303 //The stored artist sort name string takes precedence 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;
317 const std::vector<std::string> CSong::GetMusicBrainzArtistID() const
319 //Get artist MusicBrainz IDs as vector from artist credits
320 std::vector<std::string> musicBrainzID;
321 for (const auto& artistCredit : artistCredits)
323 musicBrainzID.push_back(artistCredit.GetMusicBrainzArtistID());
325 return musicBrainzID;
328 const std::string CSong::GetArtistString() const
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;
343 const std::vector<int> CSong::GetArtistIDArray() const
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;
352 void CSong::AppendArtistRole(const CMusicRole& musicRole)
354 m_musicRoles.push_back(musicRole);
357 bool CSong::HasArt() const
359 if (!strThumb.empty()) return true;
360 if (!embeddedArt.Empty()) return true;
361 return false;
364 bool CSong::ArtMatches(const CSong &right) const
366 return (right.strThumb == strThumb &&
367 embeddedArt.Matches(right.embeddedArt));
370 const std::string CSong::GetDiscSubtitle() const
372 return strDiscSubtitle;