summaryrefslogtreecommitdiffstats
path: root/xbmc/music/Song.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xbmc/music/Song.cpp373
1 files changed, 373 insertions, 0 deletions
diff --git a/xbmc/music/Song.cpp b/xbmc/music/Song.cpp
new file mode 100644
index 0000000..165889c
--- /dev/null
+++ b/xbmc/music/Song.cpp
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "Song.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "music/tags/MusicInfoTag.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+using namespace MUSIC_INFO;
+
+CSong::CSong(CFileItem& item)
+{
+ CMusicInfoTag& tag = *item.GetMusicInfoTag();
+ strTitle = tag.GetTitle();
+ genre = tag.GetGenre();
+ strArtistDesc = tag.GetArtistString();
+ //Set sort string before processing artist credits
+ strArtistSort = tag.GetArtistSort();
+ m_strComposerSort = tag.GetComposerSort();
+
+ // Determine artist credits from various tag arrays
+ SetArtistCredits(tag.GetArtist(), tag.GetMusicBrainzArtistHints(), tag.GetMusicBrainzArtistID());
+
+ strAlbum = tag.GetAlbum();
+ m_albumArtist = tag.GetAlbumArtist();
+ // Separate album artist names further, if possible, and trim blank space.
+ if (tag.GetMusicBrainzAlbumArtistHints().size() > m_albumArtist.size())
+ // Make use of hints (ALBUMARTISTS tag), when present, to separate artist names
+ m_albumArtist = tag.GetMusicBrainzAlbumArtistHints();
+ else
+ // Split album artist names further using multiple possible delimiters, over single separator applied in Tag loader
+ m_albumArtist = StringUtils::SplitMulti(m_albumArtist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicArtistSeparators);
+ for (auto artistname : m_albumArtist)
+ StringUtils::Trim(artistname);
+ m_strAlbumArtistSort = tag.GetAlbumArtistSort();
+
+ strMusicBrainzTrackID = tag.GetMusicBrainzTrackID();
+ m_musicRoles = tag.GetContributors();
+ strComment = tag.GetComment();
+ strCueSheet = tag.GetCueSheet();
+ strMood = tag.GetMood();
+ rating = tag.GetRating();
+ userrating = tag.GetUserrating();
+ votes = tag.GetVotes();
+ strOrigReleaseDate = tag.GetOriginalDate();
+ strReleaseDate = tag.GetReleaseDate();
+ strDiscSubtitle = tag.GetDiscSubtitle();
+ iTrack = tag.GetTrackAndDiscNumber();
+ iDuration = tag.GetDuration();
+ strRecordLabel = tag.GetRecordLabel();
+ strAlbumType = tag.GetMusicBrainzReleaseType();
+ bCompilation = tag.GetCompilation();
+ embeddedArt = tag.GetCoverArtInfo();
+ strFileName = tag.GetURL().empty() ? item.GetPath() : tag.GetURL();
+ dateAdded = tag.GetDateAdded();
+ replayGain = tag.GetReplayGain();
+ strThumb = item.GetUserMusicThumb(true);
+ iStartOffset = static_cast<int>(item.GetStartOffset());
+ iEndOffset = static_cast<int>(item.GetEndOffset());
+ idSong = -1;
+ iTimesPlayed = 0;
+ idAlbum = -1;
+ iBPM = tag.GetBPM();
+ iSampleRate = tag.GetSampleRate();
+ iBitRate = tag.GetBitRate();
+ iChannels = tag.GetNoOfChannels();
+}
+
+CSong::CSong()
+{
+ Clear();
+}
+
+void CSong::SetArtistCredits(const std::vector<std::string>& names, const std::vector<std::string>& hints,
+ const std::vector<std::string>& mbids)
+{
+ artistCredits.clear();
+ std::vector<std::string> artistHints = hints;
+ //Split the artist sort string to try and get sort names for individual artists
+ std::vector<std::string> artistSort = StringUtils::Split(strArtistSort, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
+
+ if (!mbids.empty())
+ { // Have musicbrainz artist info, so use it
+
+ // Vector of possible separators in the order least likely to be part of artist name
+ const std::vector<std::string> separators{ " feat. ", " ft. ", " Feat. "," Ft. ", ";", ":", "|", "#", "/", " with ", ",", "&" };
+
+ // Establish tag consistency - do the number of musicbrainz ids and number of names in hints or artist match
+ if (mbids.size() != artistHints.size() && mbids.size() != names.size())
+ {
+ // Tags mismatch - report it and then try to fix
+ CLog::Log(LOGDEBUG, "Mismatch in song file tags: {} mbid {} names {} {}", (int)mbids.size(),
+ (int)names.size(), strTitle, strArtistDesc);
+ /*
+ Most likely we have no hints and a single artist name like "Artist1 feat. Artist2"
+ or "Composer; Conductor, Orchestra, Soloist" or "Artist1/Artist2" where the
+ expected single item separator (default = space-slash-space) as not been used.
+ Ampersand (&), comma and slash (no spaces) are poor delimiters as could be in name
+ e.g. "AC/DC", "Earth, Wind & Fire", but here treat them as such in attempt to find artist names.
+ When there are hints but count not match mbid they could be poorly formatted using unexpected
+ separators so attempt to split them. Or we could have more hints or artist names than
+ musicbrainz id so ignore them but raise warning.
+ */
+ // Do hints exist yet mismatch
+ if (artistHints.size() > 0 &&
+ artistHints.size() != mbids.size())
+ {
+ if (names.size() == mbids.size())
+ // Artist name count matches, use that as hints
+ artistHints = names;
+ else if (artistHints.size() < mbids.size())
+ { // Try splitting the hints until have matching number
+ artistHints = StringUtils::SplitMulti(artistHints, separators, mbids.size());
+ }
+ else
+ // Extra hints, discard them.
+ artistHints.resize(mbids.size());
+ }
+ // Do hints not exist or still mismatch, try artists
+ if (artistHints.size() != mbids.size())
+ artistHints = names;
+ // Still mismatch, try splitting the hints (now artists) until have matching number
+ if (artistHints.size() < mbids.size())
+ {
+ artistHints = StringUtils::SplitMulti(artistHints, separators, mbids.size());
+ }
+ }
+ else
+ { // Either hints or artist names (or both) matches number of musicbrainz id
+ // If hints mismatch, use artists
+ if (artistHints.size() != mbids.size())
+ artistHints = names;
+ }
+
+ // Try to get number of artist sort names and musicbrainz ids to match. Split sort names
+ // further using multiple possible delimiters, over single separator applied in Tag loader
+ if (artistSort.size() != mbids.size())
+ artistSort = StringUtils::SplitMulti(artistSort, { ";", ":", "|", "#" });
+
+ for (size_t i = 0; i < mbids.size(); i++)
+ {
+ std::string artistId = mbids[i];
+ std::string artistName;
+ /*
+ We try and get the corresponding artist name from the hints list.
+ Having already attempted to make the number of hints match, if they
+ still don't then use musicbrainz id as the name and hope later on we
+ can update that entry.
+ */
+ if (i < artistHints.size())
+ artistName = artistHints[i];
+ else
+ artistName = artistId;
+
+ // Use artist sort name providing we have as many as we have mbid,
+ // otherwise something is wrong with them so ignore and leave blank
+ if (artistSort.size() == mbids.size())
+ artistCredits.emplace_back(StringUtils::Trim(artistName), StringUtils::Trim(artistSort[i]), artistId);
+ else
+ artistCredits.emplace_back(StringUtils::Trim(artistName), "", artistId);
+ }
+ }
+ else
+ { // No musicbrainz artist ids, so fill in directly
+ // Separate artist names further, if possible, and trim blank space.
+ std::vector<std::string> artists = names;
+ if (artistHints.size() > names.size())
+ // Make use of hints (ARTISTS tag), when present, to separate artist names
+ artists = artistHints;
+ else
+ // Split artist names further using multiple possible delimiters, over single separator applied in Tag loader
+ artists = StringUtils::SplitMulti(artists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicArtistSeparators);
+
+ if (artistSort.size() != artists.size())
+ // Split artist sort names further using multiple possible delimiters, over single separator applied in Tag loader
+ artistSort = StringUtils::SplitMulti(artistSort, { ";", ":", "|", "#" });
+
+ for (size_t i = 0; i < artists.size(); i++)
+ {
+ artistCredits.emplace_back(StringUtils::Trim(artists[i]));
+ // Set artist sort name providing we have as many as we have artists,
+ // otherwise something is wrong with them so ignore rather than guess.
+ if (artistSort.size() == artists.size())
+ artistCredits.back().SetSortName(StringUtils::Trim(artistSort[i]));
+ }
+ }
+
+}
+
+void CSong::MergeScrapedSong(const CSong& source, bool override)
+{
+ // Merge when MusicBrainz Track ID match (checked in CAlbum::MergeScrapedAlbum)
+ if ((override && !source.strTitle.empty()) || strTitle.empty())
+ strTitle = source.strTitle;
+ if ((override && source.iTrack != 0) || iTrack == 0)
+ iTrack = source.iTrack;
+ if (override)
+ {
+ artistCredits = source.artistCredits; // Replace artists and store mbid returned by scraper
+ strArtistDesc.clear(); // @todo: set artist display string e.g. "artist1 feat. artist2" when scraped
+ }
+}
+
+void CSong::Serialize(CVariant& value) const
+{
+ value["filename"] = strFileName;
+ value["title"] = strTitle;
+ value["artist"] = GetArtist();
+ value["artistsort"] = GetArtistSort(); // a string for the song not vector of values for each artist
+ value["album"] = strAlbum;
+ value["albumartist"] = GetAlbumArtist();
+ value["genre"] = genre;
+ value["duration"] = iDuration;
+ value["track"] = iTrack;
+ value["year"] = atoi(strReleaseDate.c_str());;
+ value["musicbrainztrackid"] = strMusicBrainzTrackID;
+ value["comment"] = strComment;
+ value["mood"] = strMood;
+ value["rating"] = rating;
+ value["userrating"] = userrating;
+ value["votes"] = votes;
+ value["timesplayed"] = iTimesPlayed;
+ value["lastplayed"] = lastPlayed.IsValid() ? lastPlayed.GetAsDBDateTime() : "";
+ value["dateadded"] = dateAdded.IsValid() ? dateAdded.GetAsDBDateTime() : "";
+ value["albumid"] = idAlbum;
+ value["albumreleasedate"] = strReleaseDate;
+ value["bpm"] = iBPM;
+ value["bitrate"] = iBitRate;
+ value["samplerate"] = iSampleRate;
+ value["channels"] = iChannels;
+}
+
+void CSong::Clear()
+{
+ strFileName.clear();
+ strTitle.clear();
+ strAlbum.clear();
+ strArtistSort.clear();
+ strArtistDesc.clear();
+ m_albumArtist.clear();
+ m_strAlbumArtistSort.clear();
+ genre.clear();
+ strThumb.clear();
+ strMusicBrainzTrackID.clear();
+ m_musicRoles.clear();
+ strComment.clear();
+ strMood.clear();
+ rating = 0;
+ userrating = 0;
+ votes = 0;
+ iTrack = 0;
+ iDuration = 0;
+ strOrigReleaseDate.clear();
+ strReleaseDate.clear();
+ strDiscSubtitle.clear();
+ iStartOffset = 0;
+ iEndOffset = 0;
+ idSong = -1;
+ iTimesPlayed = 0;
+ lastPlayed.Reset();
+ dateAdded.Reset();
+ dateUpdated.Reset();
+ dateNew.Reset();
+ idAlbum = -1;
+ bCompilation = false;
+ embeddedArt.Clear();
+ iBPM = 0;
+ iBitRate = 0;
+ iSampleRate = 0;
+ iChannels = 0;
+
+ replayGain = ReplayGain();
+}
+const std::vector<std::string> CSong::GetArtist() const
+{
+ //Get artist names as vector from artist credits
+ std::vector<std::string> songartists;
+ for (const auto& artistCredit : artistCredits)
+ {
+ songartists.push_back(artistCredit.GetArtist());
+ }
+ //When artist credits have not been populated attempt to build an artist vector from the description string
+ //This is a temporary fix, in the longer term other areas should query the song_artist table and populate
+ //artist credits. Note that splitting the string may not give the same artists as held in the song_artist table
+ if (songartists.empty() && !strArtistDesc.empty())
+ songartists = StringUtils::Split(strArtistDesc, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
+ return songartists;
+}
+
+const std::string CSong::GetArtistSort() const
+{
+ //The stored artist sort name string takes precedence but a
+ //value could be created from individual sort names held in artistcredits
+ if (!strArtistSort.empty())
+ return strArtistSort;
+ std::vector<std::string> artistvector;
+ for (const auto& artistcredit : artistCredits)
+ if (!artistcredit.GetSortName().empty())
+ artistvector.emplace_back(artistcredit.GetSortName());
+ std::string artistString;
+ if (!artistvector.empty())
+ artistString = StringUtils::Join(artistvector, "; ");
+ return artistString;
+}
+
+const std::vector<std::string> CSong::GetMusicBrainzArtistID() const
+{
+ //Get artist MusicBrainz IDs as vector from artist credits
+ std::vector<std::string> musicBrainzID;
+ for (const auto& artistCredit : artistCredits)
+ {
+ musicBrainzID.push_back(artistCredit.GetMusicBrainzArtistID());
+ }
+ return musicBrainzID;
+}
+
+const std::string CSong::GetArtistString() const
+{
+ //Artist description may be different from the artists in artistcredits (see ARTISTS tag processing)
+ //but is takes precedence as a string because artistcredits is not always filled during processing
+ if (!strArtistDesc.empty())
+ return strArtistDesc;
+ std::vector<std::string> artistvector;
+ for (const auto& i : artistCredits)
+ artistvector.push_back(i.GetArtist());
+ std::string artistString;
+ if (!artistvector.empty())
+ artistString = StringUtils::Join(artistvector, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator);
+ return artistString;
+}
+
+const std::vector<int> CSong::GetArtistIDArray() const
+{
+ // Get song artist IDs for json rpc
+ std::vector<int> artistids;
+ for (const auto& artistCredit : artistCredits)
+ artistids.push_back(artistCredit.GetArtistId());
+ return artistids;
+}
+
+void CSong::AppendArtistRole(const CMusicRole& musicRole)
+{
+ m_musicRoles.push_back(musicRole);
+}
+
+bool CSong::HasArt() const
+{
+ if (!strThumb.empty()) return true;
+ if (!embeddedArt.Empty()) return true;
+ return false;
+}
+
+bool CSong::ArtMatches(const CSong &right) const
+{
+ return (right.strThumb == strThumb &&
+ embeddedArt.Matches(right.embeddedArt));
+}
+
+const std::string CSong::GetDiscSubtitle() const
+{
+ return strDiscSubtitle;
+}