From b7cee6072de901e67d50690a10cbc588df86fceb Mon Sep 17 00:00:00 2001 From: DaveTBlake Date: Thu, 15 Oct 2015 19:28:02 +0100 Subject: [PATCH] Handling of tags giving people that contribute to the recording e.g. Composer, Conductor, Lyricist, Mixer etc. including TIPL and TMCL held as vector of artist name and role in CMusicInfoTag and CSong. Contributors are held in library as artists with role in song_artist and role tables determining which tag was source of data. Remove JoinPhrase and boolFeatured as unused elsewhere, artist string now stored rather than built. Queries changed to generally access only standard artists not other roles Contributors included in both JSON and Info Labels Add example/default role nodes and modified SongInfoDialog so data visible. --- .../resources/strings.po | 56 ++ .../skin.confluence/720p/DialogSongInfo.xml | 553 +++++++++----- system/library/music/musicroles/Arrangers.xml | 6 + system/library/music/musicroles/Composers.xml | 6 + .../library/music/musicroles/Conductors.xml | 6 + system/library/music/musicroles/DJMixers.xml | 6 + system/library/music/musicroles/Lyricists.xml | 6 + .../library/music/musicroles/Orchestras.xml | 6 + system/library/music/musicroles/Remixers.xml | 6 + system/library/music/musicroles/index.xml | 5 + xbmc/GUIInfoManager.cpp | 67 +- xbmc/GUIInfoManager.h | 4 + xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp | 8 + xbmc/guiinfo/GUIInfoLabels.h | 12 +- xbmc/interfaces/json-rpc/AudioLibrary.cpp | 26 + xbmc/interfaces/json-rpc/AudioLibrary.h | 1 + .../json-rpc/JSONServiceDescription.cpp | 1 + xbmc/interfaces/json-rpc/schema/methods.json | 26 +- xbmc/interfaces/json-rpc/schema/types.json | 53 +- xbmc/interfaces/json-rpc/schema/version.txt | 2 +- xbmc/music/Album.cpp | 32 +- xbmc/music/Album.h | 2 +- xbmc/music/Artist.h | 44 +- xbmc/music/MusicDatabase.cpp | 707 +++++++++++++----- xbmc/music/MusicDatabase.h | 30 +- xbmc/music/Song.cpp | 25 +- xbmc/music/Song.h | 16 +- xbmc/music/dialogs/GUIDialogMusicInfo.cpp | 2 +- xbmc/music/dialogs/GUIDialogSongInfo.cpp | 2 +- xbmc/music/infoscanner/MusicInfoScanner.cpp | 3 +- xbmc/music/tags/MusicInfoTag.cpp | 120 ++- xbmc/music/tags/MusicInfoTag.h | 15 +- xbmc/music/tags/TagLoaderTagLib.cpp | 168 ++++- xbmc/music/tags/TagLoaderTagLib.h | 3 + xbmc/playlists/SmartPlayList.cpp | 9 + xbmc/utils/DatabaseUtils.h | 1 + 36 files changed, 1556 insertions(+), 479 deletions(-) create mode 100644 system/library/music/musicroles/Arrangers.xml create mode 100644 system/library/music/musicroles/Composers.xml create mode 100644 system/library/music/musicroles/Conductors.xml create mode 100644 system/library/music/musicroles/DJMixers.xml create mode 100644 system/library/music/musicroles/Lyricists.xml create mode 100644 system/library/music/musicroles/Orchestras.xml create mode 100644 system/library/music/musicroles/Remixers.xml create mode 100644 system/library/music/musicroles/index.xml diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 35d6af0839c2c..89342fc89a667 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -14113,6 +14113,7 @@ msgstr "" #. Concert composer of the music (if present) #: addons/skin.confluence/720p/DialogPVRRadioRDSInfo.xml +#: addons/skin.confluence/720p/DialogSongInfo.xml msgctxt "#29903" msgid "Composer" msgstr "" @@ -14125,6 +14126,7 @@ msgstr "" #. Conductor of the classic music (if present) #: addons/skin.confluence/720p/DialogPVRRadioRDSInfo.xml +#: addons/skin.confluence/720p/DialogSongInfo.xml msgctxt "#29905" msgid "Conductor" msgstr "" @@ -18247,3 +18249,57 @@ msgstr "" msgctxt "#38032" msgid "Angle" msgstr "" + +#: xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp +#: xbmc/playlists/SmartPlaylist.cpp +msgctxt "#38033" +msgid "Role" +msgstr "" + +#. Orchestra playing on the recording (if present) +#: addons/skin.confluence/720p/DialogSongInfo.xml +msgctxt "#38034" +msgid "Orchestra" +msgstr "" + +#. Lyricist that wrote the lyrics of the song (if present) +#: addons/skin.confluence/720p/DialogSongInfo.xml +msgctxt "#38035" +msgid "Lyricist" +msgstr "" + +#. Remixer of the song (if present) +#: addons/skin.confluence/720p/DialogSongInfo.xml +msgctxt "#38036" +msgid "Remixer" +msgstr "" + +#. Arranger of the song (if present) +#: addons/skin.confluence/720p/DialogSongInfo.xml +msgctxt "#38037" +msgid "Arranger" +msgstr "" + +#. Engineer of the song (if present) +#: addons/skin.confluence/720p/DialogSongInfo.xml +msgctxt "#38038" +msgid "Engineer" +msgstr "" + +#. Producer of the song (if present) +#: addons/skin.confluence/720p/DialogSongInfo.xml +msgctxt "#38039" +msgid "Producer" +msgstr "" + +#. DJMixer of the song (if present) +#: addons/skin.confluence/720p/DialogSongInfo.xml +msgctxt "#38040" +msgid "DJMixer" +msgstr "" + +#. Mixer of the song (if present) +#: addons/skin.confluence/720p/DialogSongInfo.xml +msgctxt "#38041" +msgid "Mixer" +msgstr "" \ No newline at end of file diff --git a/addons/skin.confluence/720p/DialogSongInfo.xml b/addons/skin.confluence/720p/DialogSongInfo.xml index a432bc3404299..cf4739affb203 100644 --- a/addons/skin.confluence/720p/DialogSongInfo.xml +++ b/addons/skin.confluence/720p/DialogSongInfo.xml @@ -1,230 +1,369 @@ - 12 - - 185 - 105 - - dialogeffect + 5 + ClearProperty(ShowContributor,songinformation) - VisibleFadeEffect - !Window.IsVisible(MusicInformation) - - - - - - - - - - - Song Title value - 40 - 60 - 830 - 30 - center - center - font13 - - white - true - + DepthSideBlade + !Window.IsVisible(musicinformation) + WindowOpen + WindowClose - 40 - 120 - 200 - 200 - keep - $INFO[ListItem.Icon] + 180 + 0 + 1120 + 720 + MediaBladeSub.png - - 40 - 320 - 200 - 200 - keep - $INFO[ListItem.Icon] + + Close Window button + 230 + 0 + 64 + 32 + + - + PreviousMenu + DialogCloseButton-focus.png + DialogCloseButton.png + 9000 + 9000 + 9000 + 9000 + system.getbool(input.enablemouse) - - 250 - 120 - 640 - 330 - - - 200 - - + + WindowOpen + WindowClose + + Title header label + 210 + 50 + 1030 + 30 + font24_title + + center + center + white + black + + + 90 + 210 + VisibleFadeEffect + 0 0 - 150 - 30 - font13 - right - center - blue - selected - ListItem.Label + 380 + 360 + keep + button-nofocus.png + 4 + $INFO[ListItem.Icon] - - 160 - 0 - 460 - 30 - font13 - left - center - white - white - ListItem.Label2 + + 390 + 20 + 640 + 330 + 49 + 49 + 9000 + 61 + - + 200 + + + 0 + 0 + 140 + 30 + font13 + right + center + blue + selected + ListItem.Label + + + 150 + 0 + 500 + 30 + font13 + left + center + white + white + ListItem.Label2 + + + + + 0 + 0 + 640 + 30 + Control.HasFocus(49) + MenuItemFO.png + VisibleFadeEffect + + + 0 + 0 + 140 + 30 + font13 + right + center + blue + selected + ListItem.Label + + + 150 + 0 + 500 + 30 + font13 + left + center + white + white + ListItem.Label2 + + + + + + $INFO[ListItem.Artist] + noop + !IsEmpty(ListItem.Artist) + + + + $INFO[ListItem.Album]$INFO[listitem.discnumber, - $LOCALIZE[427] ] + noop + !IsEmpty(ListItem.Album) + + + + $INFO[ListItem.Genre] + noop + !IsEmpty(ListItem.Genre) + + + + $INFO[ListItem.Year] + noop + !IsEmpty(ListItem.Year) + + + + $INFO[ListItem.TrackNumber] + noop + !IsEmpty(ListItem.TrackNumber) + + + + $INFO[ListItem.RatingAndVotes] + noop + !IsEmpty(ListItem.RatingAndVotes) + + + + $INFO[ListItem.Userrating] + noop + !IsEmpty(ListItem.Userrating) + + + + $INFO[ListItem.Property(Role.Composer)] + noop + !IsEmpty(ListItem.Property(Role.Composer)) + + + + $INFO[ListItem.Property(Role.Conductor)] + noop + !IsEmpty(ListItem.Property(Role.Conductor)) + + + + $INFO[ListItem.Property(Role.Orchestra)] + noop + !IsEmpty(ListItem.Property(Role.Orchestra)) + + + + $INFO[ListItem.Property(Role.Lyricist)] + noop + !IsEmpty(ListItem.Property(Role.Lyricist)) + + + + $INFO[ListItem.Property(Role.Remixer)] + noop + !IsEmpty(ListItem.Property(Role.Remixer)) + + + + $INFO[ListItem.Property(Role.Arranger)] + noop + !IsEmpty(ListItem.Property(Role.Arranger)) + + + + $INFO[ListItem.Property(Role.Engineer)] + noop + !IsEmpty(ListItem.Property(Role.Engineer)) + + + + $INFO[ListItem.Property(Role.Producer)] + noop + !IsEmpty(ListItem.Property(Role.Producer)) + + + + $INFO[ListItem.Property(Role.DJMixer)] + noop + !IsEmpty(ListItem.Property(Role.DJMixer)) + + + + $INFO[ListItem.Property(Role.Mixer)] + noop + !IsEmpty(ListItem.Property(Role.Mixer)) + + - - - 0 - 0 + 390 + 370 640 - 30 - Control.HasFocus(49) - MenuItemFO.png - VisibleFadeEffect - - - 0 - 0 - 150 - 30 - font13 - right - center - blue - selected - ListItem.Label + 4 + stretch + separator.png - - 160 - 0 - 460 - 30 - font13 - left - center - white - white - ListItem.Label2 - - - - - - $INFO[ListItem.Artist] - noop - !IsEmpty(ListItem.Artist) - - - - $INFO[ListItem.Album]$INFO[listitem.discnumber, - $LOCALIZE[427] ] - noop - !IsEmpty(ListItem.Album) - - - - $INFO[ListItem.Genre] - noop - !IsEmpty(ListItem.Genre) - - - - $INFO[ListItem.Year] - noop - !IsEmpty(ListItem.Year) - - - - $INFO[ListItem.TrackNumber] - noop - !IsEmpty(ListItem.TrackNumber) - - - - $INFO[ListItem.RatingAndVotes] - noop - !IsEmpty(ListItem.RatingAndVotes) - - - - $INFO[ListItem.Userrating] - noop - !IsEmpty(ListItem.Userrating) - - - - - Comment Title - 40 - 360 - 150 - 25 - right - center - font13 - blue - - !IsEmpty(ListItem.Comment) - - - Comment value - 200 - 360 - 670 - 75 - font13 - - - - - - 140 - 445 - - Album Info button - 0 - 0 - 200 - 40 - - font12_title - center - 7 - 13 - - Get Thumb button + + 130 + 480 + 400 + 30 + font13_title + grey2 + black + true + right + center + + IsEmpty(Window(songinformation).Property(ShowContributor)) + + + 130 + 480 + 400 + 30 + font13_title + grey2 + black + true + right + center + + !IsEmpty(Window(songinformation).Property(ShowContributor)) + + + Next page button + 120r + 485 + page + - + 61 + 61 + 9000 + 49 + - + true + + + Comment 210 - 0 - 200 - 40 - - font12_title - center - 12 - 7 + 517 + 1030 + 122 + font13 + justify + white + 61 + + !Control.HasFocus(61) + Skin.HasSetting(AutoScroll) + IsEmpty(Window(songinformation).Property(ShowContributor)) - - Set my rating - 420 - 0 - 200 + + Contributors + 210 + 517 + 1030 + 122 + font13 + justify + white + 61 + + !Control.HasFocus(61) + Skin.HasSetting(AutoScroll) + !IsEmpty(Window(songinformation).Property(ShowContributor)) + + + 210 + 660 + 1030 40 - - font12_title + 2 center - 13 - 12 - Control.IsEnabled(7) + horizontal + 9000 + 9000 + 61 + 49 + + Comment/Contributor + ButtonInfoDialogsCommonValues + button-focus.png + button-nofocus.png + button-focus.png + button-nofocus.png + SetProperty(ShowContributor,1,songinformation) + ClearProperty(ShowContributor,songinformation) + !IsEmpty(Window(songinformation).Property(ShowContributor)) + + 569 + + + Album Info + ButtonInfoDialogsCommonValues + + + + Get Thumb + ButtonInfoDialogsCommonValues + + + + Set my rating + ButtonInfoDialogsCommonValues + + Control.IsEnabled(7) + + + DepthSideBlade + Clock + - + \ No newline at end of file diff --git a/system/library/music/musicroles/Arrangers.xml b/system/library/music/musicroles/Arrangers.xml new file mode 100644 index 0000000000000..0118e2a2fdf91 --- /dev/null +++ b/system/library/music/musicroles/Arrangers.xml @@ -0,0 +1,6 @@ + + + + DefaultMusicGenres.png + musicdb://artists/?role=Arranger + diff --git a/system/library/music/musicroles/Composers.xml b/system/library/music/musicroles/Composers.xml new file mode 100644 index 0000000000000..d73742435f48f --- /dev/null +++ b/system/library/music/musicroles/Composers.xml @@ -0,0 +1,6 @@ + + + + DefaultMusicGenres.png + musicdb://artists/?role=Composer + diff --git a/system/library/music/musicroles/Conductors.xml b/system/library/music/musicroles/Conductors.xml new file mode 100644 index 0000000000000..7cf445256e0c0 --- /dev/null +++ b/system/library/music/musicroles/Conductors.xml @@ -0,0 +1,6 @@ + + + + DefaultMusicGenres.png + musicdb://artists/?role=Conductor + diff --git a/system/library/music/musicroles/DJMixers.xml b/system/library/music/musicroles/DJMixers.xml new file mode 100644 index 0000000000000..3afe2d32690bb --- /dev/null +++ b/system/library/music/musicroles/DJMixers.xml @@ -0,0 +1,6 @@ + + + + DefaultMusicGenres.png + musicdb://artists/?role=DJMixer + diff --git a/system/library/music/musicroles/Lyricists.xml b/system/library/music/musicroles/Lyricists.xml new file mode 100644 index 0000000000000..19e324b4077cf --- /dev/null +++ b/system/library/music/musicroles/Lyricists.xml @@ -0,0 +1,6 @@ + + + + DefaultMusicGenres.png + musicdb://artists/?role=Lyricist + diff --git a/system/library/music/musicroles/Orchestras.xml b/system/library/music/musicroles/Orchestras.xml new file mode 100644 index 0000000000000..ed90d31af0145 --- /dev/null +++ b/system/library/music/musicroles/Orchestras.xml @@ -0,0 +1,6 @@ + + + + DefaultMusicGenres.png + musicdb://artists/?role=Orchestra + diff --git a/system/library/music/musicroles/Remixers.xml b/system/library/music/musicroles/Remixers.xml new file mode 100644 index 0000000000000..1dd4f6c12f977 --- /dev/null +++ b/system/library/music/musicroles/Remixers.xml @@ -0,0 +1,6 @@ + + + + DefaultMusicGenres.png + musicdb://artists/?role=Remixer + diff --git a/system/library/music/musicroles/index.xml b/system/library/music/musicroles/index.xml new file mode 100644 index 0000000000000..c6743751ac35c --- /dev/null +++ b/system/library/music/musicroles/index.xml @@ -0,0 +1,5 @@ + + + + DefaultMusicSongs.png + diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 948ad30b319ca..2fd7055131ab4 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -366,6 +366,7 @@ const infomap musicplayer[] = {{ "title", MUSICPLAYER_TITLE }, { "userrating", MUSICPLAYER_USER_RATING }, { "votes", MUSICPLAYER_VOTES }, { "comment", MUSICPLAYER_COMMENT }, + { "mood", MUSICPLAYER_MOOD }, { "lyrics", MUSICPLAYER_LYRICS }, { "playlistplaying", MUSICPLAYER_PLAYLISTPLAYING }, { "exists", MUSICPLAYER_EXISTS }, @@ -502,6 +503,8 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "albumartist", LISTITEM_ALBUM_ARTIST }, { "year", LISTITEM_YEAR }, { "genre", LISTITEM_GENRE }, + { "contributors", LISTITEM_CONTRIBUTORS }, + { "contributorandrole", LISTITEM_CONTRIBUTOR_AND_ROLE }, { "director", LISTITEM_DIRECTOR }, { "filename", LISTITEM_FILENAME }, { "filenameandpath", LISTITEM_FILENAME_AND_PATH }, @@ -513,6 +516,7 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "ratingandvotes", LISTITEM_RATING_AND_VOTES }, { "userrating", LISTITEM_USER_RATING }, { "votes", LISTITEM_VOTES }, + { "mood", LISTITEM_MOOD }, { "programcount", LISTITEM_PROGRAM_COUNT }, { "duration", LISTITEM_DURATION }, { "isselected", LISTITEM_ISSELECTED }, @@ -1145,6 +1149,8 @@ int CGUIInfoManager::TranslateSingleString(const std::string &strCondition, bool else if (cat == "moviesets") return LIBRARY_HAS_MOVIE_SETS; else if (cat == "singles") return LIBRARY_HAS_SINGLES; else if (cat == "compilations") return LIBRARY_HAS_COMPILATIONS; + else if (cat == "role" && prop.num_params() > 1) + return AddMultiInfo(GUIInfo(LIBRARY_HAS_ROLE, ConditionalStringParameter(prop.param(1)), 0)); } } else if (cat.name == "musicplayer") @@ -1528,6 +1534,11 @@ std::string CGUIInfoManager::GetLabel(int info, int contextWindow, std::string * return ""; std::string property = m_listitemProperties[info - LISTITEM_PROPERTY_START-MUSICPLAYER_PROPERTY_OFFSET]; + if (StringUtils::StartsWithNoCase(property, "Role.") && m_currentFile->HasMusicInfoTag()) + { // "Role.xxxx" properties are held in music tag + property.erase(0, 5); //Remove Role. + return m_currentFile->GetMusicInfoTag()->GetArtistStringForRole(property); + } return m_currentFile->GetProperty(property).asString(); } @@ -1740,6 +1751,8 @@ std::string CGUIInfoManager::GetLabel(int info, int contextWindow, std::string * case MUSICPLAYER_RATING_AND_VOTES: case MUSICPLAYER_USER_RATING: case MUSICPLAYER_COMMENT: + case MUSICPLAYER_CONTRIBUTORS: + case MUSICPLAYER_CONTRIBUTOR_AND_ROLE: case MUSICPLAYER_LYRICS: case MUSICPLAYER_CHANNEL_NAME: case MUSICPLAYER_CHANNEL_NUMBER: @@ -3378,6 +3391,32 @@ bool CGUIInfoManager::GetMultiInfoBool(const GUIInfo &info, int contextWindow, c bReturn = g_playlistPlayer.GetRepeat(playlistid) == PLAYLIST::REPEAT_ONE; } break; + case LIBRARY_HAS_ROLE: + { + std::string strRole = m_stringParameters[info.GetData1()]; + // Find value for role if already stored + int artistcount = -1; + for (const auto &role : m_libraryRoleCounts) + { + if (StringUtils::EqualsNoCase(strRole, role.first)) + { + artistcount = role.second; + break; + } + } + // Otherwise get from DB and store + if (artistcount < 0) + { + CMusicDatabase db; + if (db.Open()) + { + artistcount = db.GetArtistCountForRole(strRole); + db.Close(); + m_libraryRoleCounts.push_back(std::make_pair(strRole, artistcount)); + } + } + bReturn = artistcount > 0; + } } } return (info.m_info < 0) ? !bReturn : bReturn; @@ -4143,6 +4182,7 @@ std::string CGUIInfoManager::GetMusicTagLabel(int info, const CFileItem *item) { if (!item->HasMusicInfoTag()) return ""; const CMusicInfoTag &tag = *item->GetMusicInfoTag(); + switch (info) { case MUSICPLAYER_TITLE: @@ -4199,6 +4239,12 @@ std::string CGUIInfoManager::GetMusicTagLabel(int info, const CFileItem *item) return GetItemLabel(item, LISTITEM_USER_RATING); case MUSICPLAYER_COMMENT: return GetItemLabel(item, LISTITEM_COMMENT); + case MUSICPLAYER_MOOD: + return GetItemLabel(item, LISTITEM_MOOD); + case MUSICPLAYER_CONTRIBUTORS: + return GetItemLabel(item, LISTITEM_CONTRIBUTORS); + case MUSICPLAYER_CONTRIBUTOR_AND_ROLE: + return GetItemLabel(item, LISTITEM_CONTRIBUTOR_AND_ROLE); case MUSICPLAYER_DURATION: return GetItemLabel(item, LISTITEM_DURATION); case MUSICPLAYER_CHANNEL_NAME: @@ -5036,8 +5082,14 @@ std::string CGUIInfoManager::GetItemLabel(const CFileItem *item, int info, std:: } if (info >= LISTITEM_PROPERTY_START && info - LISTITEM_PROPERTY_START < (int)m_listitemProperties.size()) - { // grab the property + { std::string property = m_listitemProperties[info - LISTITEM_PROPERTY_START]; + if (StringUtils::StartsWithNoCase(property, "Role.") && item->HasMusicInfoTag()) + { // "Role.xxxx" properties are held in music tag + property.erase(0, 5); //Remove Role. + return item->GetMusicInfoTag()->GetArtistStringForRole(property); + } + // grab the property return item->GetProperty(property).asString(); } @@ -5143,6 +5195,14 @@ std::string CGUIInfoManager::GetItemLabel(const CFileItem *item, int info, std:: if (item->HasMusicInfoTag()) return item->GetMusicInfoTag()->GetAlbumArtistString(); break; + case LISTITEM_CONTRIBUTORS: + if (item->HasMusicInfoTag() && item->GetMusicInfoTag()->HasContributors()) + return item->GetMusicInfoTag()->GetContributorsText(); + break; + case LISTITEM_CONTRIBUTOR_AND_ROLE: + if (item->HasMusicInfoTag() && item->GetMusicInfoTag()->HasContributors()) + return item->GetMusicInfoTag()->GetContributorsAndRolesText(); + break; case LISTITEM_DIRECTOR: if (item->HasPVRChannelInfoTag()) { @@ -5485,6 +5545,10 @@ std::string CGUIInfoManager::GetItemLabel(const CFileItem *item, int info, std:: if (item->HasMusicInfoTag()) return item->GetMusicInfoTag()->GetComment(); break; + case LISTITEM_MOOD: + if (item->HasMusicInfoTag()) + return item->GetMusicInfoTag()->GetMood(); + break; case LISTITEM_ACTUAL_ICON: return item->GetIconImage(); case LISTITEM_ICON: @@ -6257,6 +6321,7 @@ void CGUIInfoManager::ResetLibraryBools() m_libraryHasMovieSets = -1; m_libraryHasSingles = -1; m_libraryHasCompilations = -1; + m_libraryRoleCounts.clear(); } bool CGUIInfoManager::GetLibraryBool(int condition) diff --git a/xbmc/GUIInfoManager.h b/xbmc/GUIInfoManager.h index 2fe35bbea148a..5f30ac3e93b1c 100644 --- a/xbmc/GUIInfoManager.h +++ b/xbmc/GUIInfoManager.h @@ -347,6 +347,10 @@ friend CSetCurrentItemJob; int m_libraryHasMovieSets; int m_libraryHasSingles; int m_libraryHasCompilations; + + //Count of artists in music library contributing to song by role e.g. composers, conductors etc. + //For checking visibiliy of custom nodes for a role. + std::vector> m_libraryRoleCounts; SPlayerVideoStreamInfo m_videoInfo; SPlayerAudioStreamInfo m_audioInfo; diff --git a/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp index 2b958e75b50fb..1836a49858a26 100644 --- a/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp +++ b/xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp @@ -159,6 +159,14 @@ void CGUIDialogSmartPlaylistRule::OnBrowse() } iLabel = 515; } + else if (m_rule.m_field == FieldRole) + { + if (m_type == "artists" || m_type == "mixed") + { + database.GetRolesNav("musicdb://songs/", items); + iLabel = 38027; + } + } else if (m_rule.m_field == FieldCountry) { videodatabase.GetCountriesNav(basePath, items, type); diff --git a/xbmc/guiinfo/GUIInfoLabels.h b/xbmc/guiinfo/GUIInfoLabels.h index aad3daea5473e..b107607854d71 100644 --- a/xbmc/guiinfo/GUIInfoLabels.h +++ b/xbmc/guiinfo/GUIInfoLabels.h @@ -191,6 +191,9 @@ #define MUSICPLAYER_USER_RATING 235 #define MUSICPLAYER_RATING_AND_VOTES 236 #define MUSICPLAYER_VOTES 237 +#define MUSICPLAYER_MOOD 238 +#define MUSICPLAYER_CONTRIBUTORS 239 +#define MUSICPLAYER_CONTRIBUTOR_AND_ROLE 240 #define VIDEOPLAYER_TITLE 250 #define VIDEOPLAYER_GENRE 251 @@ -385,6 +388,7 @@ #define LIBRARY_IS_SCANNING 728 #define LIBRARY_IS_SCANNING_VIDEO 729 #define LIBRARY_IS_SCANNING_MUSIC 730 +#define LIBRARY_HAS_ROLE 735 #define SYSTEM_PLATFORM_LINUX 741 #define SYSTEM_PLATFORM_WINDOWS 742 @@ -721,6 +725,10 @@ #define LISTITEM_SET (LISTITEM_START + 155) #define LISTITEM_SETID (LISTITEM_START + 156) #define LISTITEM_IS_PARENTFOLDER (LISTITEM_START + 157) +#define LISTITEM_MOOD (LISTITEM_START + 158) +#define LISTITEM_CONTRIBUTORS (LISTITEM_START + 159) +#define LISTITEM_CONTRIBUTOR_AND_ROLE (LISTITEM_START + 160) + #define LISTITEM_PROPERTY_START (LISTITEM_START + 200) #define LISTITEM_PROPERTY_END (LISTITEM_PROPERTY_START + 1300) @@ -728,8 +736,8 @@ #define MUSICPLAYER_PROPERTY_OFFSET 800 // 100 id's reserved for musicplayer props. #define LISTITEM_ART_OFFSET 900 // 100 id's reserved for listitem art. -#define LISTITEM_RATING_OFFSET 1000 // 100 id's reserved for listitem ratings. -#define LISTITEM_VOTES_OFFSET 1100 // 100 id's reserved for listitem votes. +#define LISTITEM_RATING_OFFSET 1000 // 100 id's reserved for listitem ratings. +#define LISTITEM_VOTES_OFFSET 1100 // 100 id's reserved for listitem votes. #define LISTITEM_RATING_AND_VOTES_OFFSET 1200 // 100 id's reserved for listitem ratingandvotes. #define CONDITIONAL_LABEL_START LISTITEM_END + 1 // 36501 diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.cpp b/xbmc/interfaces/json-rpc/AudioLibrary.cpp index 1a1368000444f..b6d9aab777741 100644 --- a/xbmc/interfaces/json-rpc/AudioLibrary.cpp +++ b/xbmc/interfaces/json-rpc/AudioLibrary.cpp @@ -57,6 +57,10 @@ JSONRPC_STATUS CAudioLibrary::GetArtists(const std::string &method, ITransportLa genreID = (int)filter["genreid"].asInteger(); else if (filter.isMember("genre")) musicUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("roleid")) + musicUrl.AddOption("roleid", (int)filter["roleid"].asInteger()); + else if (filter.isMember("role")) + musicUrl.AddOption("role", filter["role"].asString()); else if (filter.isMember("albumid")) albumID = (int)filter["albumid"].asInteger(); else if (filter.isMember("album")) @@ -246,6 +250,10 @@ JSONRPC_STATUS CAudioLibrary::GetSongs(const std::string &method, ITransportLaye musicUrl.AddOption("genreid", (int)filter["genreid"].asInteger()); else if (filter.isMember("genre")) musicUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("roleid")) + musicUrl.AddOption("roleid", (int)filter["roleid"].asInteger()); + else if (filter.isMember("role")) + musicUrl.AddOption("role", filter["role"].asString()); else if (filter.isMember("albumid")) musicUrl.AddOption("albumid", (int)filter["albumid"].asInteger()); else if (filter.isMember("album")) @@ -419,6 +427,24 @@ JSONRPC_STATUS CAudioLibrary::GetGenres(const std::string &method, ITransportLay return OK; } +JSONRPC_STATUS CAudioLibrary::GetContributorRoles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CFileItemList items; + if (!musicdatabase.GetRolesNav("musicdb://songs/", items)) + return InternalError; + + /* need to set strTitle in each item*/ + for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) + items[i]->GetMusicInfoTag()->SetTitle(items[i]->GetLabel()); + + HandleFileItemList("roleid", false, "roles", items, parameterObject, result); + return OK; +} + JSONRPC_STATUS CAudioLibrary::SetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) { int id = (int)parameterObject["artistid"].asInteger(); diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.h b/xbmc/interfaces/json-rpc/AudioLibrary.h index 73cbd25db9215..2a6dd2934dd42 100644 --- a/xbmc/interfaces/json-rpc/AudioLibrary.h +++ b/xbmc/interfaces/json-rpc/AudioLibrary.h @@ -39,6 +39,7 @@ namespace JSONRPC static JSONRPC_STATUS GetSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); static JSONRPC_STATUS GetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); static JSONRPC_STATUS GetGenres(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetContributorRoles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); static JSONRPC_STATUS GetRecentlyAddedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); static JSONRPC_STATUS GetRecentlyAddedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); diff --git a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp index 4e5edfe207510..d07cf49151c44 100644 --- a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp +++ b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp @@ -111,6 +111,7 @@ JsonRpcMethodMap CJSONServiceDescription::m_methodMaps[] = { { "AudioLibrary.GetRecentlyPlayedAlbums", CAudioLibrary::GetRecentlyPlayedAlbums }, { "AudioLibrary.GetRecentlyPlayedSongs", CAudioLibrary::GetRecentlyPlayedSongs }, { "AudioLibrary.GetGenres", CAudioLibrary::GetGenres }, + { "AudioLibrary.GetContributorRoles", CAudioLibrary::GetContributorRoles }, { "AudioLibrary.SetArtistDetails", CAudioLibrary::SetArtistDetails }, { "AudioLibrary.SetAlbumDetails", CAudioLibrary::SetAlbumDetails }, { "AudioLibrary.SetSongDetails", CAudioLibrary::SetSongDetails }, diff --git a/xbmc/interfaces/json-rpc/schema/methods.json b/xbmc/interfaces/json-rpc/schema/methods.json index bd35ffb241eae..1168f64635238 100644 --- a/xbmc/interfaces/json-rpc/schema/methods.json +++ b/xbmc/interfaces/json-rpc/schema/methods.json @@ -645,7 +645,7 @@ "transport": "Response", "permission": "ReadData", "params": [ - { "name": "albumartistsonly", "$ref": "Optional.Boolean", "description": "Whether or not to include artists only appearing in compilations. If the parameter is not passed or is passed as null the GUI setting will be used" }, + { "name": "albumartistsonly", "$ref": "Optional.Boolean", "description": "Whether or not to only include album artists rather than the artists of individual songs as well. If the parameter is not passed or is passed as null the GUI setting will be used" }, { "name": "properties", "$ref": "Audio.Fields.Artist" }, { "name": "limits", "$ref": "List.Limits" }, { "name": "sort", "$ref": "List.Sort" }, @@ -656,6 +656,8 @@ { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, { "type": "object", "properties": { "album": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, { "type": "object", "properties": { "songid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "roleid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "role": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, { "$ref": "List.Filter.Artists" } ] } @@ -747,6 +749,8 @@ { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, { "type": "object", "properties": { "album": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "roleid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "role": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, { "$ref": "List.Filter.Songs" } ] }, @@ -878,6 +882,26 @@ } } }, + "AudioLibrary.GetContributorRoles": { + "type": "method", + "description": "Retrieve all contributor roles", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Library.Fields.Role" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "roles": { "type": "array", "required": true, + "items": { "$ref": "Library.Details.Role" } + } + } + } + }, "AudioLibrary.SetArtistDetails": { "type": "method", "description": "Update the given artist with the given details", diff --git a/xbmc/interfaces/json-rpc/schema/types.json b/xbmc/interfaces/json-rpc/schema/types.json index 21bda46cc5f94..f00c5397ec4cd 100644 --- a/xbmc/interfaces/json-rpc/schema/types.json +++ b/xbmc/interfaces/json-rpc/schema/types.json @@ -404,6 +404,17 @@ "thumbnail": { "type": "string" } } }, + "Library.Fields.Role": { + "extends": "Item.Fields.Base", + "items": { "type": "string", "enum": [ "title" ] } + }, + "Library.Details.Role": { + "extends": "Item.Details.Base", + "properties": { + "roleid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" } + } + }, "Audio.Fields.Artist": { "extends": "Item.Fields.Base", "items": { "type": "string", @@ -430,12 +441,15 @@ "items": { "type": "string", "description": "Requesting the genreid, artistid and/or albumartistid field will result in increased response times", "enum": [ "title", "artist", "albumartist", "genre", "year", - "rating", "votes", "userrating", "album", "track", "duration", "comment", + "rating", "album", "track", "duration", "comment", "lyrics", "musicbrainztrackid", "musicbrainzartistid", "musicbrainzalbumid", "musicbrainzalbumartistid", "playcount", "fanart", "thumbnail", "file", "albumid", "lastplayed", "disc", "genreid", "artistid", "displayartist", - "albumartistid", "albumreleasetype", "dateadded" ] + "albumartistid", "albumreleasetype", "dateadded", + "votes", "userrating", "mood", "contributors", + "displaycomposer", "displayconductor", "displayorchestra", "displaylyricist" + ] } }, "Audio.Album.ReleaseType": { @@ -443,6 +457,17 @@ "enum": [ "album", "single" ], "default": "album" }, + "Audio.Contributors": { + "type": "array", + "items": { "type": "object", + "properties": { + "name": { "type": "string", "required": true }, + "role": { "type": "string", "required": true }, + "artistid": { "type": "integer", "required": true } + }, + "additionalProperties": false + } + }, "Audio.Details.Base": { "extends": "Media.Details.Base", "properties": { @@ -457,13 +482,13 @@ "artist": { "$ref": "Array.String" }, "year": { "type": "integer" }, "rating": { "type": "number" }, - "votes": { "type": "integer" }, - "userrating": { "type": "integer" }, "musicbrainzalbumid": { "type": "string" }, "musicbrainzalbumartistid": { "type": "string" }, "genreid": { "$ref": "Array.Integer" }, "artistid": { "$ref": "Array.Integer" }, - "displayartist": { "type" : "string" } + "displayartist": { "type" : "string" }, + "votes": { "type": "integer" }, + "userrating": { "type": "integer" } } }, "Audio.Details.Artist": { @@ -517,7 +542,13 @@ "lastplayed": { "type": "string" }, "disc": { "type": "integer" }, "albumartistid": { "$ref": "Array.Integer" }, - "albumreleasetype": { "$ref": "Audio.Album.ReleaseType" } + "albumreleasetype": { "$ref": "Audio.Album.ReleaseType" }, + "mood": { "type": "string"}, + "contributors": { "$ref": "Audio.Contributors" }, + "displaycomposer": { "type": "string"}, + "displayconductor": { "type": "string"}, + "displayorchestra": { "type": "string"}, + "displaylyricist": { "type": "string"} } }, "Video.Fields.Movie": { @@ -1284,7 +1315,12 @@ "specialsortepisode": { "type": "integer" }, "compilation": { "type": "boolean" }, "releasetype": { "$ref": "Audio.Album.ReleaseType" }, - "albumreleasetype": { "$ref": "Audio.Album.ReleaseType" } + "albumreleasetype": { "$ref": "Audio.Album.ReleaseType" }, + "contributors": { "$ref": "Audio.Contributors" }, + "displaycomposer": { "type": "string"}, + "displayconductor": { "type": "string"}, + "displayorchestra": { "type": "string"}, + "displaylyricist": { "type": "string"} } }, "List.Fields.All": { @@ -1303,7 +1339,8 @@ "description", "theme", "mood", "style", "albumlabel", "sorttitle", "episodeguide", "uniqueid", "dateadded", "channel", "channeltype", "hidden", "locked", "channelnumber", "starttime", "endtime", "specialsortseason", - "specialsortepisode", "compilation", "releasetype", "albumreleasetype" ] + "specialsortepisode", "compilation", "releasetype", "albumreleasetype", + "contributors", "displaycomposer", "displayconductor", "displayorchestra", "displaylyricist" ] } }, "List.Item.All": { diff --git a/xbmc/interfaces/json-rpc/schema/version.txt b/xbmc/interfaces/json-rpc/schema/version.txt index ba7f754d0c33e..a2931d315b990 100644 --- a/xbmc/interfaces/json-rpc/schema/version.txt +++ b/xbmc/interfaces/json-rpc/schema/version.txt @@ -1 +1 @@ -7.4.0 +7.5.0 \ No newline at end of file diff --git a/xbmc/music/Album.cpp b/xbmc/music/Album.cpp index 85e8027c2d3d8..390511a5bcfdc 100644 --- a/xbmc/music/Album.cpp +++ b/xbmc/music/Album.cpp @@ -90,8 +90,7 @@ CAlbum::CAlbum(const CFileItem& item) if (artistName.empty()) artistName = artistId; - std::string strJoinPhrase = (i == tag.GetMusicBrainzAlbumArtistID().size()-1) ? "" : g_advancedSettings.m_musicItemSeparator; - CArtistCredit artistCredit(artistName, tag.GetMusicBrainzAlbumArtistID()[i], strJoinPhrase); + CArtistCredit artistCredit(artistName, tag.GetMusicBrainzAlbumArtistID()[i]); artistCredits.push_back(artistCredit); } } @@ -99,11 +98,10 @@ CAlbum::CAlbum(const CFileItem& item) { // no musicbrainz info, so fill in directly for (std::vector::const_iterator it = tag.GetAlbumArtist().begin(); it != tag.GetAlbumArtist().end(); ++it) { - std::string strJoinPhrase = (it == --tag.GetAlbumArtist().end() ? "" : g_advancedSettings.m_musicItemSeparator); - CArtistCredit artistCredit(*it, "", strJoinPhrase); - artistCredits.push_back(artistCredit); + artistCredits.emplace_back(*it); } } + iYear = stTime.wYear; bCompilation = tag.GetCompilation(); iTimesPlayed = 0; @@ -200,9 +198,12 @@ const std::string CAlbum::GetAlbumArtistString() const //but is takes precidence as a string because artistcredits is not always filled during processing if (!strArtistDesc.empty()) return strArtistDesc; + std::vector artistvector(artistCredits.size()); + for (VECARTISTCREDITS::const_iterator i = artistCredits.begin(); i != artistCredits.end(); ++i) + artistvector.emplace_back(i->GetArtist()); std::string artistString; - for (VECARTISTCREDITS::const_iterator artistCredit = artistCredits.begin(); artistCredit != artistCredits.end(); ++artistCredit) - artistString += artistCredit->GetArtist() + artistCredit->GetJoinPhrase(); + if (!artistvector.empty()) + artistString = StringUtils::Join(artistvector, g_advancedSettings.m_musicItemSeparator); return artistString; } @@ -215,6 +216,7 @@ const std::vector CAlbum::GetArtistIDArray() const return artistids; } + std::string CAlbum::GetReleaseType() const { return ReleaseTypeToString(releaseType); @@ -285,6 +287,7 @@ bool CAlbum::Load(const TiXmlElement *album, bool append, bool prioritise) XMLUtils::GetString(album, "title", strAlbum); XMLUtils::GetString(album, "musicBrainzAlbumID", strMusicBrainzAlbumID); + XMLUtils::GetString(album, "artistdesc", strArtistDesc); std::vector artist; // Support old style for backwards compatibility XMLUtils::GetStringArray(album, "artist", artist, prioritise, g_advancedSettings.m_musicItemSeparator); XMLUtils::GetStringArray(album, "genre", genre, prioritise, g_advancedSettings.m_musicItemSeparator); @@ -359,8 +362,6 @@ bool CAlbum::Load(const TiXmlElement *album, bool append, bool prioritise) CArtistCredit artistCredit; XMLUtils::GetString(albumArtistCreditsNode, "artist", artistCredit.m_strArtist); XMLUtils::GetString(albumArtistCreditsNode, "musicBrainzArtistID", artistCredit.m_strMusicBrainzArtistID); - XMLUtils::GetString(albumArtistCreditsNode, "joinphrase", artistCredit.m_strJoinPhrase); - XMLUtils::GetBoolean(albumArtistCreditsNode, "featuring", artistCredit.m_boolFeatured); artistCredits.push_back(artistCredit); } @@ -374,8 +375,7 @@ bool CAlbum::Load(const TiXmlElement *album, bool append, bool prioritise) { for (std::vector::const_iterator it = artist.begin(); it != artist.end(); ++it) { - CArtistCredit artistCredit(*it, "", - it == --artist.end() ? "" : g_advancedSettings.m_musicItemSeparator); + CArtistCredit artistCredit(*it); artistCredits.push_back(artistCredit); } } @@ -402,8 +402,6 @@ bool CAlbum::Load(const TiXmlElement *album, bool append, bool prioritise) CArtistCredit artistCredit; XMLUtils::GetString(songArtistCreditsNode, "artist", artistCredit.m_strArtist); XMLUtils::GetString(songArtistCreditsNode, "musicBrainzArtistID", artistCredit.m_strMusicBrainzArtistID); - XMLUtils::GetString(songArtistCreditsNode, "joinphrase", artistCredit.m_strJoinPhrase); - XMLUtils::GetBoolean(songArtistCreditsNode, "featuring", artistCredit.m_boolFeatured); song.artistCredits.push_back(artistCredit); } @@ -450,7 +448,7 @@ bool CAlbum::Save(TiXmlNode *node, const std::string &tag, const std::string& st XMLUtils::SetString(album, "title", strAlbum); XMLUtils::SetString(album, "musicBrainzAlbumID", strMusicBrainzAlbumID); - XMLUtils::SetStringArray(album, "artist", GetAlbumArtist()); + XMLUtils::SetString(album, "artistdesc", strArtistDesc); //Can be different from artist credits XMLUtils::SetStringArray(album, "genre", genre); XMLUtils::SetStringArray(album, "style", styles); XMLUtils::SetStringArray(album, "mood", moods); @@ -487,10 +485,8 @@ bool CAlbum::Save(TiXmlNode *node, const std::string &tag, const std::string& st TiXmlNode *albumArtistCreditsNode = album->InsertEndChild(albumArtistCreditsElement); XMLUtils::SetString(albumArtistCreditsNode, "artist", artistCredit->m_strArtist); XMLUtils::SetString(albumArtistCreditsNode, "musicBrainzArtistID", artistCredit->m_strMusicBrainzArtistID); - XMLUtils::SetString(albumArtistCreditsNode, "joinphrase", artistCredit->m_strJoinPhrase); - XMLUtils::SetString(albumArtistCreditsNode, "featuring", artistCredit->GetArtist()); } - + for( VECSONGS::const_iterator song = infoSongs.begin(); song != infoSongs.end(); ++song) { // add a tag @@ -503,8 +499,6 @@ bool CAlbum::Save(TiXmlNode *node, const std::string &tag, const std::string& st TiXmlNode *songArtistCreditsNode = node->InsertEndChild(songArtistCreditsElement); XMLUtils::SetString(songArtistCreditsNode, "artist", artistCredit->m_strArtist); XMLUtils::SetString(songArtistCreditsNode, "musicBrainzArtistID", artistCredit->m_strMusicBrainzArtistID); - XMLUtils::SetString(songArtistCreditsNode, "joinphrase", artistCredit->m_strJoinPhrase); - XMLUtils::SetString(songArtistCreditsNode, "featuring", artistCredit->GetArtist()); } XMLUtils::SetString(node, "musicBrainzTrackID", song->strMusicBrainzTrackID); XMLUtils::SetString(node, "title", song->strTitle); diff --git a/xbmc/music/Album.h b/xbmc/music/Album.h index b507c47058ed2..0407986726a11 100644 --- a/xbmc/music/Album.h +++ b/xbmc/music/Album.h @@ -94,7 +94,7 @@ class CAlbum \return album artist names as a single string */ const std::string GetAlbumArtistString() const; - + /*! \brief Get album artist IDs (for json rpc) from the vector of artistcredits objects \return album artist IDs as a vector of integers */ diff --git a/xbmc/music/Artist.h b/xbmc/music/Artist.h index 834033d95bc76..15be40cb32ea3 100644 --- a/xbmc/music/Artist.h +++ b/xbmc/music/Artist.h @@ -25,6 +25,7 @@ #include #include +#include "utils/StringUtils.h" #include "utils/Fanart.h" #include "utils/ScraperUrl.h" #include "XBDateTime.h" @@ -111,9 +112,10 @@ class CArtistCredit public: CArtistCredit() { } - CArtistCredit(std::string strArtist, std::string strJoinPhrase) : m_strArtist(strArtist), m_strJoinPhrase(strJoinPhrase), m_boolFeatured(false) { } - CArtistCredit(std::string strArtist, std::string strMusicBrainzArtistID, std::string strJoinPhrase) - : m_strArtist(strArtist), m_strMusicBrainzArtistID(strMusicBrainzArtistID), m_strJoinPhrase(strJoinPhrase), m_boolFeatured(false) { } + CArtistCredit(std::string strArtist) : m_strArtist(strArtist) { } + CArtistCredit(std::string strArtist, std::string strMusicBrainzArtistID) + : m_strArtist(strArtist), m_strMusicBrainzArtistID(strMusicBrainzArtistID) { } + bool operator<(const CArtistCredit& a) const { if (m_strMusicBrainzArtistID.empty() && a.m_strMusicBrainzArtistID.empty()) @@ -130,21 +132,49 @@ class CArtistCredit std::string GetArtist() const { return m_strArtist; } std::string GetMusicBrainzArtistID() const { return m_strMusicBrainzArtistID; } - std::string GetJoinPhrase() const { return m_strJoinPhrase; } int GetArtistId() const { return idArtist; } void SetArtist(const std::string &strArtist) { m_strArtist = strArtist; } void SetMusicBrainzArtistID(const std::string &strMusicBrainzArtistID) { m_strMusicBrainzArtistID = strMusicBrainzArtistID; } - void SetJoinPhrase(const std::string &strJoinPhrase) { m_strJoinPhrase = strJoinPhrase; } void SetArtistId(int idArtist) { this->idArtist = idArtist; } private: long idArtist; std::string m_strArtist; std::string m_strMusicBrainzArtistID; - std::string m_strJoinPhrase; - bool m_boolFeatured; }; typedef std::vector VECARTISTS; typedef std::vector VECARTISTCREDITS; +#define ROLE_ARTIST 1 //Default role + +class CMusicRole +{ +public: + CMusicRole() { } + CMusicRole(std::string strRole, std::string strArtist) : idRole(-1), m_strRole(strRole), m_strArtist(strArtist), idArtist(-1) { } + CMusicRole(int role, std::string strRole, std::string strArtist, long ArtistId) : idRole(role), m_strRole(strRole), m_strArtist(strArtist), idArtist(ArtistId) { } + std::string GetArtist() const { return m_strArtist; } + std::string GetRoleDesc() const { return m_strRole; } + int GetRoleId() const { return idRole; } + long GetArtistId() const { return idArtist; } + void SetArtistId(long iArtistId) { idArtist = iArtistId; } + + bool operator==(const CMusicRole& a) const + { + if (StringUtils::EqualsNoCase(m_strRole, a.m_strRole)) + return StringUtils::EqualsNoCase(m_strArtist, a.m_strArtist); + else + return false; + } +private: + int idRole; + std::string m_strRole; + std::string m_strArtist; + long idArtist; +}; + +typedef std::vector VECMUSICROLES; + + + diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index 2596222a586a9..cc87e4ae25079 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -70,6 +70,7 @@ using namespace XFILE; using namespace MUSICDATABASEDIRECTORY; using namespace KODI::MESSAGING; +using namespace MUSIC_INFO; using ADDON::AddonPtr; using KODI::MESSAGING::HELPERS::DialogResponse; @@ -141,7 +142,7 @@ void CMusicDatabase::CreateTables() " strReleaseType text, " " iVotes INTEGER NOT NULL DEFAULT 0)"); CLog::Log(LOGINFO, "create album_artist table"); - m_pDS->exec("CREATE TABLE album_artist (idArtist integer, idAlbum integer, strJoinPhrase text, boolFeatured integer, iOrder integer, strArtist text)"); + m_pDS->exec("CREATE TABLE album_artist (idArtist integer, idAlbum integer, iOrder integer, strArtist text)"); CLog::Log(LOGINFO, "create album_genre table"); m_pDS->exec("CREATE TABLE album_genre (idGenre integer, idAlbum integer, iOrder integer)"); @@ -162,10 +163,16 @@ void CMusicDatabase::CreateTables() " rating FLOAT NOT NULL DEFAULT 0, userrating INTEGER NOT NULL DEFAULT 0, " " comment text, mood text, dateAdded text, votes INTEGER NOT NULL DEFAULT 0)"); CLog::Log(LOGINFO, "create song_artist table"); - m_pDS->exec("CREATE TABLE song_artist (idArtist integer, idSong integer, strJoinPhrase text, boolFeatured integer, iOrder integer, strArtist text)"); + m_pDS->exec("CREATE TABLE song_artist (idArtist integer, idSong integer, idRole integer, iOrder integer, strArtist text)"); CLog::Log(LOGINFO, "create song_genre table"); m_pDS->exec("CREATE TABLE song_genre (idGenre integer, idSong integer, iOrder integer)"); + CLog::Log(LOGINFO, "create role table"); + m_pDS->exec("CREATE TABLE role (idRole integer primary key, strRole text)"); + m_pDS->exec("INSERT INTO role(idRole, strRole) VALUES (1, 'Artist')"); //Default role + m_pDS->exec("INSERT INTO role(idRole, strRole) VALUES (2, 'Composer')"); + m_pDS->exec("INSERT INTO role(idRole, strRole) VALUES (3, 'Conductor')"); + CLog::Log(LOGINFO, "create albuminfosong table"); m_pDS->exec("CREATE TABLE albuminfosong (idAlbumInfoSong integer primary key, idAlbumInfo integer, iTrack integer, strTitle text, iDuration integer)"); @@ -190,7 +197,6 @@ void CMusicDatabase::CreateAnalytics() m_pDS->exec("CREATE UNIQUE INDEX idxAlbumArtist_1 ON album_artist ( idAlbum, idArtist )"); m_pDS->exec("CREATE UNIQUE INDEX idxAlbumArtist_2 ON album_artist ( idArtist, idAlbum )"); - m_pDS->exec("CREATE INDEX idxAlbumArtist_3 ON album_artist ( boolFeatured )"); m_pDS->exec("CREATE UNIQUE INDEX idxAlbumGenre_1 ON album_genre ( idAlbum, idGenre )"); m_pDS->exec("CREATE UNIQUE INDEX idxAlbumGenre_2 ON album_genre ( idGenre, idAlbum )"); @@ -209,13 +215,14 @@ void CMusicDatabase::CreateAnalytics() m_pDS->exec("CREATE INDEX idxSong6 ON song( idPath, strFileName(255) )"); m_pDS->exec("CREATE UNIQUE INDEX idxSong7 ON song( idAlbum, strMusicBrainzTrackID(36) )"); - m_pDS->exec("CREATE UNIQUE INDEX idxSongArtist_1 ON song_artist ( idSong, idArtist )"); - m_pDS->exec("CREATE UNIQUE INDEX idxSongArtist_2 ON song_artist ( idArtist, idSong )"); - m_pDS->exec("CREATE INDEX idxSongArtist_3 ON song_artist ( boolFeatured )"); + m_pDS->exec("CREATE UNIQUE INDEX idxSongArtist_1 ON song_artist ( idSong, idArtist, idRole )"); + m_pDS->exec("CREATE UNIQUE INDEX idxSongArtist_2 ON song_artist ( idArtist, idSong, idRole )"); m_pDS->exec("CREATE UNIQUE INDEX idxSongGenre_1 ON song_genre ( idSong, idGenre )"); m_pDS->exec("CREATE UNIQUE INDEX idxSongGenre_2 ON song_genre ( idGenre, idSong )"); + m_pDS->exec("CREATE INDEX idxRole on role(strRole(255))"); + m_pDS->exec("CREATE INDEX idxAlbumInfoSong_1 ON albuminfosong ( idAlbumInfo )"); m_pDS->exec("CREATE INDEX idxDiscography_1 ON discography ( idArtist )"); @@ -325,10 +332,10 @@ void CMusicDatabase::CreateViews() m_pDS->exec("CREATE VIEW albumartistview AS SELECT" " album_artist.idAlbum AS idAlbum, " " album_artist.idArtist AS idArtist, " + " 0 AS idRole, " + " 'AlbumArtist' AS strRole, " " artist.strArtist AS strArtist, " " artist.strMusicBrainzArtistID AS strMusicBrainzArtistID, " - " album_artist.boolFeatured AS boolFeatured, " - " album_artist.strJoinPhrase AS strJoinPhrase, " " album_artist.iOrder AS iOrder " "FROM album_artist " "JOIN artist ON " @@ -338,14 +345,16 @@ void CMusicDatabase::CreateViews() m_pDS->exec("CREATE VIEW songartistview AS SELECT" " song_artist.idSong AS idSong, " " song_artist.idArtist AS idArtist, " + " song_artist.idRole AS idRole, " + " role.strRole AS strRole, " " artist.strArtist AS strArtist, " " artist.strMusicBrainzArtistID AS strMusicBrainzArtistID, " - " song_artist.boolFeatured AS boolFeatured, " - " song_artist.strJoinPhrase AS strJoinPhrase, " " song_artist.iOrder AS iOrder " "FROM song_artist " "JOIN artist ON " - " song_artist.idArtist = artist.idArtist"); + " song_artist.idArtist = artist.idArtist " + "JOIN role ON " + " song_artist.idRole = role.idRole"); } int CMusicDatabase::AddAlbumInfoSong(int idAlbum, const CSong& song) @@ -485,8 +494,6 @@ bool CMusicDatabase::AddAlbum(CAlbum& album) AddAlbumArtist(artistCredit->idArtist, album.idAlbum, artistCredit->GetArtist(), - artistCredit->GetJoinPhrase(), - artistCredit == album.artistCredits.begin() ? false : true, std::distance(album.artistCredits.begin(), artistCredit)); } @@ -514,14 +521,16 @@ bool CMusicDatabase::AddAlbum(CAlbum& album) artistCredit->GetMusicBrainzArtistID()); AddSongArtist(artistCredit->idArtist, song->idSong, - artistCredit->GetArtist(), - artistCredit->GetJoinPhrase(), // we don't have song artist breakdowns from scrapers, yet - artistCredit == song->artistCredits.begin() ? false : true, + ROLE_ARTIST, + artistCredit->GetArtist(), // we don't have song artist breakdowns from scrapers, yet std::distance(song->artistCredits.begin(), artistCredit)); } + // Having added artist credits (maybe with MBID) add the other contributing artists (no MBID) + AddSongContributors(song->idSong, song->GetContributors()); SaveCuesheet(song->strFileName, song->strCueSheet); } + for (VECSONGS::const_iterator infoSong = album.infoSongs.begin(); infoSong != album.infoSongs.end(); ++infoSong) AddAlbumInfoSong(album.idAlbum, *infoSong); @@ -551,7 +560,7 @@ bool CMusicDatabase::UpdateAlbum(CAlbum& album, bool OverrideTagData /* = true*/ if (OverrideTagData) { - // Add the album artists + // Replace the album artists DeleteAlbumArtistsByAlbum(album.idAlbum); for (VECARTISTCREDITS::iterator artistCredit = album.artistCredits.begin(); artistCredit != album.artistCredits.end(); ++artistCredit) { @@ -560,8 +569,6 @@ bool CMusicDatabase::UpdateAlbum(CAlbum& album, bool OverrideTagData /* = true*/ AddAlbumArtist(artistCredit->idArtist, album.idAlbum, artistCredit->GetArtist(), - artistCredit->GetJoinPhrase(), - artistCredit == album.artistCredits.begin() ? false : true, std::distance(album.artistCredits.begin(), artistCredit)); } @@ -586,6 +593,7 @@ bool CMusicDatabase::UpdateAlbum(CAlbum& album, bool OverrideTagData /* = true*/ song->rating, song->userrating, song->votes); + //Replace song artists and contributors DeleteSongArtistsBySong(song->idSong); for (VECARTISTCREDITS::iterator artistCredit = song->artistCredits.begin(); artistCredit != song->artistCredits.end(); ++artistCredit) { @@ -593,11 +601,12 @@ bool CMusicDatabase::UpdateAlbum(CAlbum& album, bool OverrideTagData /* = true*/ artistCredit->GetMusicBrainzArtistID()); AddSongArtist(artistCredit->idArtist, song->idSong, + ROLE_ARTIST, artistCredit->GetArtist(), - artistCredit->GetJoinPhrase(), - artistCredit == song->artistCredits.begin() ? false : true, std::distance(song->artistCredits.begin(), artistCredit)); } + // Having added artist credits (maybe with MBID) add the other contributing artists (MBID unknown) + AddSongContributors(song->idSong, song->GetContributors()); SaveCuesheet(song->strFileName, song->strCueSheet); } @@ -726,7 +735,8 @@ bool CMusicDatabase::GetSong(int idSong, CSong& song) std::string strSQL=PrepareSQL("SELECT songview.*,songartistview.* FROM songview " " LEFT JOIN songartistview ON songview.idSong = songartistview.idSong " - " WHERE songview.idSong = %i", idSong); + " WHERE songview.idSong = %i " + " ORDER BY songartistview.idRole, songartistview.iOrder", idSong); if (!m_pDS->query(strSQL)) return false; int iRowsFound = m_pDS->num_rows(); @@ -738,18 +748,17 @@ bool CMusicDatabase::GetSong(int idSong, CSong& song) int songArtistOffset = song_enumCount; - std::set artistcredits; song = GetSongFromDataset(m_pDS.get()->get_sql_record()); while (!m_pDS->eof()) { const dbiplus::sql_record* const record = m_pDS.get()->get_sql_record(); int idSongArtist = record->at(songArtistOffset + artistCredit_idArtist).get_asInt(); - if (artistcredits.find(idSongArtist) == artistcredits.end()) - { + int idSongArtistRole = record->at(songArtistOffset + artistCredit_idRole).get_asInt(); + if (idSongArtistRole == ROLE_ARTIST) song.artistCredits.push_back(GetArtistCreditFromDataset(record, songArtistOffset)); - artistcredits.insert(idSongArtist); - } + else + song.AppendArtistRole(GetArtistRoleFromDataset(record, songArtistOffset)); m_pDS->next(); } @@ -971,26 +980,15 @@ bool CMusicDatabase::GetAlbum(int idAlbum, CAlbum& album, bool getSongs /* = tru if (idAlbum == -1) return false; // not in the database + //Get album, song and album song info data using separate queries/datasets because we can have + //multiple roles per artist for songs and that makes a single combined join impractical + //Get album data std::string sql; - if (getSongs) - { - sql = PrepareSQL("SELECT albumview.*,albumartistview.*,songview.*,songartistview.*,albuminfosong.* " - " FROM albumview " - " LEFT JOIN albumartistview ON albumview.idAlbum = albumartistview.idAlbum " - " JOIN songview ON albumview.idAlbum = songview.idAlbum " - " LEFT JOIN songartistview ON songview.idSong = songartistview.idSong " - " LEFT JOIN albuminfosong ON albumview.idAlbum = albuminfosong.idAlbumInfo " - " WHERE albumview.idAlbum = %ld " - " ORDER BY albumartistview.iOrder, songview.iTrack, songartistview.iOrder", idAlbum); - } - else - { - sql = PrepareSQL("SELECT albumview.*,albumartistview.* " - " FROM albumview " - " LEFT JOIN albumartistview ON albumview.idAlbum = albumartistview.idAlbum " - " WHERE albumview.idAlbum = %ld " - " ORDER BY albumartistview.iOrder", idAlbum); - } + sql = PrepareSQL("SELECT albumview.*,albumartistview.* " + " FROM albumview " + " LEFT JOIN albumartistview ON albumview.idAlbum = albumartistview.idAlbum " + " WHERE albumview.idAlbum = %ld " + " ORDER BY albumartistview.iOrder", idAlbum); CLog::Log(LOGDEBUG, "%s", sql.c_str()); if (!m_pDS->query(sql)) return false; @@ -1001,60 +999,79 @@ bool CMusicDatabase::GetAlbum(int idAlbum, CAlbum& album, bool getSongs /* = tru } int albumArtistOffset = album_enumCount; - int songOffset = albumArtistOffset + artistCredit_enumCount; - int songArtistOffset = songOffset + song_enumCount; - int infoSongOffset = songArtistOffset + artistCredit_enumCount; - - std::set artistcredits; - std::set songs; - std::set > songartistcredits; - std::set infosongs; + album = GetAlbumFromDataset(m_pDS.get()->get_sql_record(), 0, true); // true to grab and parse the imageURL while (!m_pDS->eof()) { const dbiplus::sql_record* const record = m_pDS->get_sql_record(); - // Because rows repeat in the joined query (cartesian join) we may see each - // entity (album artist, song, song artist) multiple times in the result set. - // Since there should never be a song with the same artist twice, or an album - // with the same song (by id) listed twice, we key on the entity ID and only - // create an entity for the first occurence of each entity in the data set. - int idAlbumArtist = record->at(albumArtistOffset + artistCredit_idArtist).get_asInt(); - if (artistcredits.find(idAlbumArtist) == artistcredits.end()) + // Album artists always have role = 0 (idRole and strRole columns are in albumartistview to match columns of songartistview) + // so there is only one row in the result set for each artist credit. + album.artistCredits.push_back(GetArtistCreditFromDataset(record, albumArtistOffset)); + + m_pDS->next(); + } + m_pDS->close(); // cleanup recordset data + + //Get song data + if (getSongs) + { + sql = PrepareSQL("SELECT songview.*, songartistview.*" + " FROM songview " + " LEFT JOIN songartistview ON songview.idSong = songartistview.idSong " + " WHERE songview.idAlbum = %ld " + " ORDER BY songview.iTrack, songartistview.idRole, songartistview.iOrder", idAlbum); + + CLog::Log(LOGDEBUG, "%s", sql.c_str()); + if (!m_pDS->query(sql)) return false; + if (m_pDS->num_rows() == 0) //Album with no songs { - album.artistCredits.push_back(GetArtistCreditFromDataset(record, albumArtistOffset)); - artistcredits.insert(idAlbumArtist); + m_pDS->close(); + return false; } - if (getSongs) + int songArtistOffset = song_enumCount; + std::set songs; + while (!m_pDS->eof()) { - int idSong = record->at(songOffset + song_idSong).get_asInt(); + const dbiplus::sql_record* const record = m_pDS->get_sql_record(); + + int idSong = record->at(song_idSong).get_asInt(); //Same as songartist.idSong by join if (songs.find(idSong) == songs.end()) { - album.songs.push_back(GetSongFromDataset(record, songOffset)); + album.songs.push_back(GetSongFromDataset(record)); songs.insert(idSong); } - int idSongArtistSong = record->at(songArtistOffset + artistCredit_idEntity).get_asInt(); - int idSongArtistArtist = record->at(songArtistOffset + artistCredit_idArtist).get_asInt(); - if (songartistcredits.find(std::make_pair(idSongArtistSong, idSongArtistArtist)) == songartistcredits.end()) - { - for (VECSONGS::iterator si = album.songs.begin(); si != album.songs.end(); ++si) - if (si->idSong == idSongArtistSong) - si->artistCredits.push_back(GetArtistCreditFromDataset(record, songArtistOffset)); - songartistcredits.insert(std::make_pair(idSongArtistSong, idSongArtistArtist)); - } + int idSongArtist = record->at(songArtistOffset + artistCredit_idArtist).get_asInt(); + int idSongArtistRole = record->at(songArtistOffset + artistCredit_idRole).get_asInt(); + //By query order song is the last one appened to the album song vector. + if (idSongArtistRole == ROLE_ARTIST) + album.songs.back().artistCredits.push_back(GetArtistCreditFromDataset(record, songArtistOffset)); + else + album.songs.back().AppendArtistRole(GetArtistRoleFromDataset(record, songArtistOffset)); - int idAlbumInfoSong = m_pDS.get()->get_sql_record()->at(infoSongOffset + albumInfoSong_idAlbumInfoSong).get_asInt(); - if (infosongs.find(idAlbumInfoSong) == infosongs.end()) - { - album.infoSongs.push_back(GetAlbumInfoSongFromDataset(record, infoSongOffset)); - infosongs.insert(idAlbumInfoSong); - } + m_pDS->next(); } - m_pDS->next(); + m_pDS->close(); // cleanup recordset data + + //Album Song Information + sql = PrepareSQL("SELECT albuminfosong.* " + " FROM albuminfosong " + " WHERE idAlbumInfo = %ld "" ORDER BY iTrack", idAlbum); + + CLog::Log(LOGDEBUG, "%s", sql.c_str()); + if (!m_pDS->query(sql)) return false; + while (!m_pDS->eof()) + { + const dbiplus::sql_record* const record = m_pDS->get_sql_record(); + + album.infoSongs.push_back(GetAlbumInfoSongFromDataset(record)); + m_pDS->next(); + } + m_pDS->close(); // cleanup recordset data } - m_pDS->close(); // cleanup recordset data + return true; } catch (...) @@ -1163,7 +1180,7 @@ int CMusicDatabase::AddArtist(const std::string& strArtist, const std::string& s if (!strMusicBrainzArtistID.empty()) { // 1.a) Match on a MusicBrainz ID - strSQL = PrepareSQL("SELECT * FROM artist WHERE strMusicBrainzArtistID = '%s'", + strSQL = PrepareSQL("SELECT idArtist, strArtist FROM artist WHERE strMusicBrainzArtistID = '%s'", strMusicBrainzArtistID.c_str()); m_pDS->query(strSQL); if (m_pDS->num_rows() > 0) @@ -1184,7 +1201,7 @@ int CMusicDatabase::AddArtist(const std::string& strArtist, const std::string& s // 1.b) No match on MusicBrainz ID. Look for a previously added artist with no MusicBrainz ID // and update that if it exists. - strSQL = PrepareSQL("SELECT * FROM artist WHERE strArtist LIKE '%s' AND strMusicBrainzArtistID IS NULL", strArtist.c_str()); + strSQL = PrepareSQL("SELECT idArtist FROM artist WHERE strArtist LIKE '%s' AND strMusicBrainzArtistID IS NULL", strArtist.c_str()); m_pDS->query(strSQL); if (m_pDS->num_rows() > 0) { @@ -1205,7 +1222,7 @@ int CMusicDatabase::AddArtist(const std::string& strArtist, const std::string& s } else { - strSQL = PrepareSQL("SELECT * FROM artist WHERE strArtist LIKE '%s'", + strSQL = PrepareSQL("SELECT idArtist FROM artist WHERE strArtist LIKE '%s'", strArtist.c_str()); m_pDS->query(strSQL); @@ -1355,26 +1372,133 @@ bool CMusicDatabase::DeleteArtistDiscography(int idArtist) return ExecuteQuery(strSQL); } -bool CMusicDatabase::AddSongArtist(int idArtist, int idSong, std::string strArtist, std::string joinPhrase, bool featured, int iOrder) +int CMusicDatabase::AddRole(const std::string &strRole) { + int idRole = -1; std::string strSQL; - strSQL=PrepareSQL("replace into song_artist (idArtist, idSong, strArtist, strJoinPhrase, boolFeatured, iOrder) values(%i,%i,'%s','%s',%i,%i)", - idArtist, idSong, strArtist.c_str(), joinPhrase.c_str(), featured == true ? 1 : 0, iOrder); + + try + { + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + strSQL = PrepareSQL("SELECT idRole FROM role WHERE strRole LIKE '%s'", strRole.c_str()); + m_pDS->query(strSQL); + if (m_pDS->num_rows() > 0) + idRole = m_pDS->fv("idRole").get_asInt(); + m_pDS->close(); + + if (idRole < 0) + { + strSQL = PrepareSQL("INSERT INTO role (strRole) VALUES ('%s')", strRole.c_str()); + m_pDS->exec(strSQL); + idRole = static_cast(m_pDS->lastinsertid()); + m_pDS->close(); + } + } + catch (...) + { + CLog::Log(LOGERROR, "musicdatabase:unable to AddRole (%s)", strSQL.c_str()); + } + return idRole; +} + +bool CMusicDatabase::AddSongArtist(int idArtist, int idSong, const std::string& strRole, const std::string& strArtist, int iOrder) +{ + int idRole = AddRole(strRole); + return AddSongArtist(idArtist, idSong, idRole, strArtist, iOrder); +} + +bool CMusicDatabase::AddSongArtist(int idArtist, int idSong, int idRole, const std::string& strArtist, int iOrder) +{ + std::string strSQL; + strSQL = PrepareSQL("replace into song_artist (idArtist, idSong, idRole, strArtist, iOrder) values(%i,%i,%i,'%s',%i)", + idArtist, idSong, idRole, strArtist.c_str(), iOrder); return ExecuteQuery(strSQL); -}; +} + +int CMusicDatabase::AddSongContributor(int idSong, const std::string& strRole, const std::string& strArtist) +{ + if (strArtist.empty()) + return -1; + + std::string strSQL; + try + { + if (NULL == m_pDB.get()) return -1; + if (NULL == m_pDS.get()) return -1; + + int idArtist = -1; + // Add artist. As we only have name (no MBID) first try to identify artist from song + // as they may have already been added with a different role (including MBID). + strSQL = PrepareSQL("SELECT idArtist FROM song_artist WHERE idSong = %i AND strArtist LIKE '%s' ", idSong, strArtist.c_str()); + m_pDS->query(strSQL); + if (m_pDS->num_rows() > 0) + idArtist = m_pDS->fv("idArtist").get_asInt(); + m_pDS->close(); + + if (idArtist < 0) + idArtist = AddArtist(strArtist, ""); + + // Add to song_artist table + AddSongArtist(idArtist, idSong, strRole, strArtist, 0); + + return idArtist; + } + catch (...) + { + CLog::Log(LOGERROR, "musicdatabase:unable to AddSongContributor (%s)", strSQL.c_str()); + } + + return -1; +} + +void CMusicDatabase::AddSongContributors(int idSong, const VECMUSICROLES& contributors) +{ + for (VECMUSICROLES::const_iterator credit = contributors.begin(); credit != contributors.end(); ++credit) + { + AddSongContributor(idSong, credit->GetRoleDesc(), credit->GetArtist()); + } +} + +int CMusicDatabase::GetRoleByName(const std::string& strRole) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + std::string strSQL; + strSQL = PrepareSQL("SELECT idRole FROM Role WHERE strRole like '%s'", strRole.c_str()); + // run query + if (!m_pDS->query(strSQL)) return false; + int iRowsFound = m_pDS->num_rows(); + if (iRowsFound != 1) + { + m_pDS->close(); + return -1; + } + return m_pDS->fv("idRole").get_asInt(); + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + } + return -1; + +} bool CMusicDatabase::DeleteSongArtistsBySong(int idSong) { return ExecuteQuery(PrepareSQL("DELETE FROM song_artist WHERE idSong = %i", idSong)); } -bool CMusicDatabase::AddAlbumArtist(int idArtist, int idAlbum, std::string strArtist, std::string joinPhrase, bool featured, int iOrder) +bool CMusicDatabase::AddAlbumArtist(int idArtist, int idAlbum, std::string strArtist, int iOrder) { std::string strSQL; - strSQL=PrepareSQL("replace into album_artist (idArtist, idAlbum, strArtist, strJoinPhrase, boolFeatured, iOrder) values(%i,%i,'%s','%s',%i,%i)", - idArtist, idAlbum, strArtist.c_str(), joinPhrase.c_str(), featured == true ? 1 : 0, iOrder); + strSQL = PrepareSQL("replace into album_artist (idArtist, idAlbum, strArtist, iOrder) values(%i,%i,'%s',%i)", + idArtist, idAlbum, strArtist.c_str(), iOrder); return ExecuteQuery(strSQL); -}; +} bool CMusicDatabase::DeleteAlbumArtistsByAlbum(int idAlbum) { @@ -1413,17 +1537,12 @@ bool CMusicDatabase::DeleteAlbumGenresByAlbum(int idAlbum) return ExecuteQuery(PrepareSQL("DELETE FROM album_genre WHERE idAlbum = %i", idAlbum)); } -bool CMusicDatabase::GetAlbumsByArtist(int idArtist, bool includeFeatured, std::vector &albums) +bool CMusicDatabase::GetAlbumsByArtist(int idArtist, std::vector &albums) { try { - std::string strSQL, strPrepSQL; - - strPrepSQL = "select idAlbum from album_artist where idArtist=%i"; - if (includeFeatured == false) - strPrepSQL += " AND boolFeatured = 0"; - - strSQL=PrepareSQL(strPrepSQL, idArtist); + std::string strSQL; + strSQL = PrepareSQL("SELECT idAlbum FROM album_artist WHERE idArtist = %i", idArtist); if (!m_pDS->query(strSQL)) return false; if (m_pDS->num_rows() == 0) @@ -1451,9 +1570,10 @@ bool CMusicDatabase::GetArtistsByAlbum(int idAlbum, CFileItem* item) { try { - std::string strSQL, strPrepSQL; + std::string strSQL; strSQL = PrepareSQL("SELECT * FROM albumartistview WHERE idAlbum = %i", idAlbum); + if (!m_pDS->query(strSQL)) return false; if (m_pDS->num_rows() == 0) @@ -1496,17 +1616,13 @@ bool CMusicDatabase::GetArtistsByAlbum(int idAlbum, CFileItem* item) return false; } -bool CMusicDatabase::GetSongsByArtist(int idArtist, bool includeFeatured, std::vector &songs) +bool CMusicDatabase::GetSongsByArtist(int idArtist, std::vector &songs) { try { - std::string strSQL, strPrepSQL; - - strPrepSQL = "select idSong from song_artist where idArtist=%i"; - if (includeFeatured == false) - strPrepSQL += " AND boolFeatured = 0"; - - strSQL=PrepareSQL(strPrepSQL, idArtist); + std::string strSQL; + //Restrict to Artists only, no other roles + strSQL = PrepareSQL("SELECT idSong FROM song_artist WHERE idArtist = %i AND idRole = 1", idArtist); if (!m_pDS->query(strSQL)) return false; if (m_pDS->num_rows() == 0) @@ -1530,17 +1646,13 @@ bool CMusicDatabase::GetSongsByArtist(int idArtist, bool includeFeatured, std::v return false; }; -bool CMusicDatabase::GetArtistsBySong(int idSong, bool includeFeatured, std::vector &artists) +bool CMusicDatabase::GetArtistsBySong(int idSong, std::vector &artists) { try { - std::string strSQL, strPrepSQL; - - strPrepSQL = "select idArtist from song_artist where idSong=%i"; - if (includeFeatured == false) - strPrepSQL += " AND boolFeatured = 0"; - - strSQL=PrepareSQL(strPrepSQL, idSong); + std::string strSQL; + //Restrict to Artists only, no other roles + strSQL = PrepareSQL("SELECT idArtist FROM song_artist WHERE idSong = %i AND idRole = 1", idSong); if (!m_pDS->query(strSQL)) return false; if (m_pDS->num_rows() == 0) @@ -1824,11 +1936,18 @@ CArtistCredit CMusicDatabase::GetArtistCreditFromDataset(const dbiplus::sql_reco artistCredit.idArtist = record->at(offset + artistCredit_idArtist).get_asInt(); artistCredit.m_strArtist = record->at(offset + artistCredit_strArtist).get_asString(); artistCredit.m_strMusicBrainzArtistID = record->at(offset + artistCredit_strMusicBrainzArtistID).get_asString(); - artistCredit.m_boolFeatured = record->at(offset + artistCredit_bFeatured).get_asBool(); - artistCredit.m_strJoinPhrase = record->at(offset + artistCredit_strJoinPhrase).get_asString(); return artistCredit; } +CMusicRole CMusicDatabase::GetArtistRoleFromDataset(const dbiplus::sql_record* const record, int offset /* = 0 */) +{ + CMusicRole ArtistRole(record->at(offset + artistCredit_idRole).get_asInt(), + record->at(offset + artistCredit_strRole).get_asString(), + record->at(offset + artistCredit_strArtist).get_asString(), + record->at(offset + artistCredit_idArtist).get_asInt()); + return ArtistRole; +} + CArtist CMusicDatabase::GetArtistFromDataset(dbiplus::Dataset* pDS, int offset /* = 0 */, bool needThumb /* = true */) { return GetArtistFromDataset(pDS->get_sql_record(), offset, needThumb); @@ -2083,9 +2202,9 @@ bool CMusicDatabase::GetTop100Albums(VECALBUMS& albums) albumId = record->at(album_idAlbum).get_asInt(); albums.push_back(GetAlbumFromDataset(record)); } - // Get artist details + // Get album artists albums.back().artistCredits.push_back(GetArtistCreditFromDataset(record, albumArtistOffset)); - + m_pDS->next(); } @@ -2179,7 +2298,7 @@ bool CMusicDatabase::GetRecentlyPlayedAlbums(VECALBUMS& albums) albumId = record->at(album_idAlbum).get_asInt(); albums.push_back(GetAlbumFromDataset(record)); } - // Get artist details + // Get album artists albums.back().artistCredits.push_back(GetArtistCreditFromDataset(record, albumArtistOffset)); m_pDS->next(); @@ -2224,6 +2343,7 @@ bool CMusicDatabase::GetRecentlyPlayedAlbumSongs(const std::string& strBaseDir, // Needs a separate query to determine number of songs to set items size. // Get songs from returned rows. Join means there is a row for every song artist + // Gather artist credits, rather than append to item as go along, so can return array of artistIDs too int songArtistOffset = song_enumCount; int songId = -1; VECARTISTCREDITS artistCredits; @@ -2231,6 +2351,7 @@ bool CMusicDatabase::GetRecentlyPlayedAlbumSongs(const std::string& strBaseDir, { const dbiplus::sql_record* const record = m_pDS->get_sql_record(); + int idSongArtistRole = record->at(songArtistOffset + artistCredit_idRole).get_asInt(); if (songId != record->at(song_idSong).get_asInt()) { //New song if (songId > 0 && !artistCredits.empty()) @@ -2244,8 +2365,12 @@ bool CMusicDatabase::GetRecentlyPlayedAlbumSongs(const std::string& strBaseDir, GetFileItemFromDataset(record, item.get(), baseUrl); items.Add(item); } - // Get song artist credits - artistCredits.push_back(GetArtistCreditFromDataset(record, songArtistOffset)); + // Get song artist credits and contributors + if (idSongArtistRole == ROLE_ARTIST) + artistCredits.push_back(GetArtistCreditFromDataset(record, songArtistOffset)); + else + items[items.Size() - 1]->GetMusicInfoTag()->AppendArtistRole(GetArtistRoleFromDataset(record, songArtistOffset)); + m_pDS->next(); } if (!artistCredits.empty()) @@ -2304,7 +2429,7 @@ bool CMusicDatabase::GetRecentlyAddedAlbums(VECALBUMS& albums, unsigned int limi albumId = record->at(album_idAlbum).get_asInt(); albums.push_back(GetAlbumFromDataset(record)); } - // Get artist details + // Get album artists albums.back().artistCredits.push_back(GetArtistCreditFromDataset(record, albumArtistOffset)); m_pDS->next(); @@ -2360,6 +2485,7 @@ bool CMusicDatabase::GetRecentlyAddedAlbumSongs(const std::string& strBaseDir, C { const dbiplus::sql_record* const record = m_pDS->get_sql_record(); + int idSongArtistRole = record->at(songArtistOffset + artistCredit_idRole).get_asInt(); if (songId != record->at(song_idSong).get_asInt()) { //New song if (songId > 0 && !artistCredits.empty()) @@ -2373,8 +2499,11 @@ bool CMusicDatabase::GetRecentlyAddedAlbumSongs(const std::string& strBaseDir, C GetFileItemFromDataset(record, item.get(), baseUrl); items.Add(item); } - // Get song artist credits - artistCredits.push_back(GetArtistCreditFromDataset(record, songArtistOffset)); + // Get song artist credits and contributors + if (idSongArtistRole == ROLE_ARTIST) + artistCredits.push_back(GetArtistCreditFromDataset(record, songArtistOffset)); + else + items[items.Size() - 1]->GetMusicInfoTag()->AppendArtistRole(GetArtistRoleFromDataset(record, songArtistOffset)); m_pDS->next(); } @@ -2789,6 +2918,24 @@ bool CMusicDatabase::CleanupGenres() return false; } +bool CMusicDatabase::CleanupRoles() +{ + try + { + // Cleanup orphaned roles (ie those that don't belong to a song entry) + // Must be executed AFTER the song, and song_srtist tables have been cleaned. + // Do not remove default role (ROLE_ARTIST) + std::string strSQL = "DELETE FROM role WHERE idRole > 1 AND idRole NOT IN (SELECT idRole FROM song_artist)"; + m_pDS->exec(strSQL); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "Exception in CMusicDatabase::CleanupRoles() or was aborted"); + } + return false; +} + bool CMusicDatabase::CleanupOrphanedItems() { // paths aren't cleaned up here - they're cleaned up in RemoveSongsFromPath() @@ -2797,6 +2944,7 @@ bool CMusicDatabase::CleanupOrphanedItems() if (!CleanupAlbums()) return false; if (!CleanupArtists()) return false; if (!CleanupGenres()) return false; + if (!CleanupRoles()) return false; return true; } @@ -2878,6 +3026,11 @@ int CMusicDatabase::Cleanup(bool bShowProgress /* = true */) ret = ERROR_REORG_GENRE; goto error; } + if (!CleanupRoles()) + { + ret = ERROR_REORG_ROLE; + goto error; + } // commit transaction if (pDlgProgress) { @@ -3171,7 +3324,7 @@ bool CMusicDatabase::GetGenresNav(const std::string& strBaseDir, CFileItemList& // run query CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); - if (!m_pDS->query(strSQL.c_str())) + if (!m_pDS->query(strSQL)) return false; int iRowsFound = m_pDS->num_rows(); if (iRowsFound == 0) @@ -3282,6 +3435,68 @@ bool CMusicDatabase::GetYearsNav(const std::string& strBaseDir, CFileItemList& i return false; } +bool CMusicDatabase::GetRolesNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter /* = Filter() */) +{ + try + { + if (NULL == m_pDB.get()) return false; + if (NULL == m_pDS.get()) return false; + + Filter extFilter = filter; + CMusicDbUrl musicUrl; + SortDescription sorting; + if (!musicUrl.FromString(strBaseDir) || !GetFilter(musicUrl, extFilter, sorting)) + return false; + + // get roles with artists having that role + std::string strSQL = "SELECT idRole, strRole FROM role WHERE EXISTS " + "(SELECT 1 FROM song_artist WHERE song_artist.idRole = role.idRole)"; + + if (!BuildSQL(strSQL, extFilter, strSQL)) + return false; + + // run query + CLog::Log(LOGDEBUG, "%s query: %s", __FUNCTION__, strSQL.c_str()); + if (!m_pDS->query(strSQL)) + return false; + int iRowsFound = m_pDS->num_rows(); + if (iRowsFound == 0) + { + m_pDS->close(); + return true; + } + + // get data from returned rows + while (!m_pDS->eof()) + { + std::string labelValue = m_pDS->fv("role.strRole").get_asString(); + CFileItemPtr pItem(new CFileItem(labelValue)); + pItem->GetMusicInfoTag()->SetTitle(labelValue); + pItem->GetMusicInfoTag()->SetDatabaseId(m_pDS->fv("role.idRole").get_asInt(), "role"); + + CMusicDbUrl itemUrl = musicUrl; + std::string strDir = StringUtils::Format("%s/", labelValue.c_str()); + itemUrl.AppendPath(strDir); + pItem->SetPath(itemUrl.ToString()); + + pItem->m_bIsFolder = true; + items.Add(pItem); + + m_pDS->next(); + } + + // cleanup + m_pDS->close(); + + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "%s failed", __FUNCTION__); + } + return false; +} + bool CMusicDatabase::GetAlbumsByYear(const std::string& strBaseDir, CFileItemList& items, int year) { CMusicDbUrl musicUrl; @@ -3797,7 +4012,7 @@ bool CMusicDatabase::GetAlbumsByWhere(const std::string &baseDir, const Filter & albumId = record->at(album_idAlbum).get_asInt(); albums.push_back(GetAlbumFromDataset(record)); } - // Get album artist credits + // Get artists albums.back().artistCredits.push_back(GetArtistCreditFromDataset(record, albumArtistOffset)); } @@ -3907,9 +4122,17 @@ bool CMusicDatabase::GetSongsFullByWhere(const std::string &baseDir, const Filte item->m_iprogramCount = ++count; items.Add(item); } - // Get song artist credits + // Get song artist credits and contributors if (artistData) - artistCredits.push_back(GetArtistCreditFromDataset(record, songArtistOffset)); + { + int idSongArtistRole = record->at(songArtistOffset + artistCredit_idRole).get_asInt(); + if (idSongArtistRole == ROLE_ARTIST) + artistCredits.push_back(GetArtistCreditFromDataset(record, songArtistOffset)); + else + items[items.Size() - 1]->GetMusicInfoTag()->AppendArtistRole(GetArtistRoleFromDataset(record, songArtistOffset)); + + } + } catch (...) { @@ -4063,7 +4286,7 @@ bool CMusicDatabase::GetSongsByYear(const std::string& baseDir, CFileItemList& i musicUrl.AddOption("year", year); Filter filter; - return GetSongsByWhere(baseDir, filter, items); + return GetSongsFullByWhere(baseDir, filter, items, SortDescription(), true); } bool CMusicDatabase::GetSongsNav(const std::string& strBaseDir, CFileItemList& items, int idGenre, int idArtist, int idAlbum, const SortDescription &sortDescription /* = SortDescription() */) @@ -4082,7 +4305,7 @@ bool CMusicDatabase::GetSongsNav(const std::string& strBaseDir, CFileItemList& i musicUrl.AddOption("artistid", idArtist); Filter filter; - return GetSongsByWhere(musicUrl.ToString(), filter, items, sortDescription); + return GetSongsFullByWhere(musicUrl.ToString(), filter, items, sortDescription, true); } void CMusicDatabase::UpdateTables(int version) @@ -4101,14 +4324,6 @@ void CMusicDatabase::UpdateTables(int version) m_pDS->exec("UPDATE song SET strMusicBrainzTrackID = NULL"); } - if (version < 35) - { - m_pDS->exec("ALTER TABLE album_artist ADD strJoinPhrase text\n"); - m_pDS->exec("ALTER TABLE song_artist ADD strJoinPhrase text\n"); - CMediaSettings::GetInstance().SetMusicNeedsUpdate(35); - CSettings::GetInstance().Save(); - } - if (version < 36) { // translate legacy musicdb:// paths @@ -4214,9 +4429,6 @@ void CMusicDatabase::UpdateTables(int version) m_pDS2->exec(PrepareSQL("UPDATE album_artist SET strArtist='%s' where idArtist=%i", m_pDS->fv(1).get_asString().c_str(), m_pDS->fv(0).get_asInt())); m_pDS->next(); } - // drop the last separator if more than one - m_pDS->exec("UPDATE song_artist SET strJoinPhrase = '' WHERE 100*idSong+iOrder IN (SELECT id FROM (SELECT 100*idSong+max(iOrder) AS id FROM song_artist GROUP BY idSong) AS sub)"); - m_pDS->exec("UPDATE album_artist SET strJoinPhrase = '' WHERE 100*idAlbum+iOrder IN (SELECT id FROM (SELECT 100*idAlbum+max(iOrder) AS id FROM album_artist GROUP BY idAlbum) AS sub)"); } if (version < 48) { // null out columns that are no longer used @@ -4407,15 +4619,37 @@ void CMusicDatabase::UpdateTables(int version) m_pDS->exec("ALTER TABLE album ADD iVotes INTEGER NOT NULL DEFAULT 0"); m_pDS->exec("ALTER TABLE song ADD votes INTEGER NOT NULL DEFAULT 0"); } - if (version < 58) - { + if (version < 58) + { m_pDS->exec("UPDATE album SET fRating = fRating * 2"); - } + } + if (version < 59) + { + m_pDS->exec("CREATE TABLE role (idRole integer primary key, strRole text)"); + m_pDS->exec("INSERT INTO role(idRole, strRole) VALUES (1, 'Artist')"); //Default Role + + //Remove strJoinPhrase, boolFeatured from song_artist table and add idRole + m_pDS->exec("CREATE TABLE song_artist_new (idArtist integer, idSong integer, idRole integer, iOrder integer, strArtist text)"); + m_pDS->exec("INSERT INTO song_artist_new (idArtist, idSong, idRole, iOrder, strArtist) " + "SELECT idArtist, idSong, 1 as idRole, iOrder, strArtist FROM song_artist"); + m_pDS->exec("DROP TABLE song_artist"); + m_pDS->exec("ALTER TABLE song_artist_new RENAME TO song_artist"); + + //Remove strJoinPhrase, boolFeatured from album_artist table + m_pDS->exec("CREATE TABLE album_artist_new (idArtist integer, idAlbum integer, iOrder integer, strArtist text)"); + m_pDS->exec("INSERT INTO album_artist_new (idArtist, idAlbum, iOrder, strArtist) " + "SELECT idArtist, idAlbum, iOrder, strArtist FROM album_artist"); + m_pDS->exec("DROP TABLE album_artist"); + m_pDS->exec("ALTER TABLE album_artist_new RENAME TO album_artist"); + + CMediaSettings::GetInstance().SetMusicNeedsUpdate(58); + CSettings::GetInstance().Save(); + } } int CMusicDatabase::GetSchemaVersion() const { - return 58; + return 59; } unsigned int CMusicDatabase::GetSongIDs(const Filter &filter, std::vector > &songIDs) @@ -4752,7 +4986,7 @@ bool CMusicDatabase::GetCompilationSongs(const std::string& strBaseDir, CFileIte musicUrl.AddOption("compilation", true); Filter filter; - return GetSongsByWhere(musicUrl.ToString(), filter, items); + return GetSongsFullByWhere(musicUrl.ToString(), filter, items, SortDescription(), true); } int CMusicDatabase::GetCompilationAlbumsCount() @@ -4766,6 +5000,18 @@ int CMusicDatabase::GetSinglesCount() return GetSongsCount(filter); } +int CMusicDatabase::GetArtistCountForRole(int role) +{ + std::string strSQL = PrepareSQL("SELECT COUNT(DISTINCT idartist) FROM song_artist WHERE song_artist.idRole = %i", role); + return strtol(GetSingleValue(strSQL).c_str(), NULL, 10); +} + +int CMusicDatabase::GetArtistCountForRole(const std::string& strRole) +{ + std::string strSQL = PrepareSQL("SELECT COUNT(DISTINCT idartist) FROM song_artist JOIN role ON song_artist.idRole = role.idRole WHERE role.strRole LIKE '%s'", strRole.c_str()); + return strtol(GetSingleValue(strSQL).c_str(), NULL, 10); +} + bool CMusicDatabase::SetPathHash(const std::string &path, const std::string &hash) { try @@ -5216,12 +5462,14 @@ bool CMusicDatabase::GetItems(const std::string &strBaseDir, const std::string & return GetGenresNav(strBaseDir, items, filter); else if (StringUtils::EqualsNoCase(itemType, "years")) return GetYearsNav(strBaseDir, items, filter); + else if (StringUtils::EqualsNoCase(itemType, "roles")) + return GetRolesNav(strBaseDir, items, filter); else if (StringUtils::EqualsNoCase(itemType, "artists")) return GetArtistsNav(strBaseDir, items, !CSettings::GetInstance().GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS), -1, -1, -1, filter, sortDescription); else if (StringUtils::EqualsNoCase(itemType, "albums")) return GetAlbumsByWhere(strBaseDir, filter, items, sortDescription); else if (StringUtils::EqualsNoCase(itemType, "songs")) - return GetSongsByWhere(strBaseDir, filter, items, sortDescription); + return GetSongsFullByWhere(strBaseDir, filter, items, sortDescription, true); return false; } @@ -5512,7 +5760,7 @@ void CMusicDatabase::ImportFromXML(const std::string &xmlFile) CAlbum album; GetAlbum(idAlbum, album, true); album.MergeScrapedAlbum(importedAlbum, true); - UpdateAlbum(album); + UpdateAlbum(album); //Will replace song artists if present in xml } current++; @@ -5719,6 +5967,26 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription const CUrlOptions::UrlOptions& options = musicUrl.GetOptions(); CUrlOptions::UrlOptions::const_iterator option; + //Process role options, common to artist and album type filtering + int idRole = 1; // Default restrict song_artist to "artists" only, no other roles. + option = options.find("roleid"); + if (option != options.end()) + idRole = (int)option->second.asInteger(); + else + { + option = options.find("role"); + if (option != options.end()) + { + if (option->second.asString() == "all") + idRole = -1000; //All roles + else + idRole = GetRoleByName(option->second.asString()); + } + } + + std::string strRoleSQL; //Role < 0 means all roles, otherwise filter by role + if(idRole > 0) strRoleSQL = PrepareSQL(" AND song_artist.idRole = %i ", idRole); + if (type == "artists") { int idArtist = -1, idGenre = -1, idAlbum = -1, idSong = -1; @@ -5760,30 +6028,59 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription if (idArtist > 0) strSQL += PrepareSQL("(%d)", idArtist); else if (idAlbum > 0) - strSQL += PrepareSQL("(SELECT album_artist.idArtist FROM album_artist WHERE album_artist.idAlbum = %i)", idAlbum); + strSQL += PrepareSQL("(SELECT album_artist.idArtist FROM album_artist" + " WHERE album_artist.idAlbum = %i)", idAlbum); else if (idSong > 0) - strSQL += PrepareSQL("(SELECT song_artist.idArtist FROM song_artist WHERE song_artist.idSong = %i)", idSong); + { + strSQL += PrepareSQL("(SELECT song_artist.idArtist FROM song_artist" + " WHERE song_artist.idSong = %i %s)", idSong, strRoleSQL.c_str()); + } else if (idGenre > 0) { // same statements as below, but limit to the specified genre - // in this case we show the whole lot always - there is no limitation to just album artists - if (!albumArtistsOnly) // show all artists in this case (ie those linked to a song) - strSQL+=PrepareSQL("(SELECT song_artist.idArtist FROM song_artist" // All artists linked to extra genres - " JOIN song_genre ON song_artist.idSong = song_genre.idSong" - " WHERE song_genre.idGenre = %i)" - " OR idArtist IN ", idGenre); - // and add any artists linked to an album (may be different from above due to album artist tag) - strSQL += PrepareSQL("(SELECT album_artist.idArtist FROM album_artist" // All album artists linked to extra genres - " JOIN album_genre ON album_artist.idAlbum = album_genre.idAlbum" - " WHERE album_genre.idGenre = %i)", idGenre); + if (albumArtistsOnly || idRole == 1) //Album artists only or no specific role, check album_artist table + strSQL += PrepareSQL("(SELECT album_artist.idArtist FROM album_artist" // All album artists linked to extra genres + " JOIN album_genre ON album_artist.idAlbum = album_genre.idAlbum" + " WHERE album_genre.idGenre = %i)", idGenre); + if (albumArtistsOnly && idRole > 1) //Album artists only with role, check AND in song_artist + strSQL += " AND artistview.idArtist IN "; + if (!albumArtistsOnly && idRole == 1) //Song and album artists no specific role, check OR in song_artist + strSQL += " OR artistview.idArtist IN "; + if (!albumArtistsOnly || idRole > 1) //Song and album artists or album artists with specific role, check song_artist + strSQL += PrepareSQL("(SELECT song_artist.idArtist FROM song_artist" // All artists linked to extra genres + " JOIN song_genre ON song_artist.idSong = song_genre.idSong" + " WHERE song_genre.idGenre = %i AND song_artist.idRole = %i )", idGenre, idRole); + + if (idRole < -1) //All artists contributing to songs, all roles. + strSQL = PrepareSQL("(artistview.idArtist IN (SELECT song_artist.idArtist FROM song_artist" + " JOIN song_genre ON song_artist.idSong = song_genre.idSong" + " WHERE song_genre.idGenre = %i)" + " OR artistview.idArtist IN" + " (SELECT album_artist.idArtist FROM album_artist" // All album artists linked to extra genres + " JOIN album_genre ON album_artist.idAlbum = album_genre.idAlbum" + " WHERE album_genre.idGenre = %i)", + idGenre, idGenre); } else - { - if (!albumArtistsOnly) // show all artists in this case (ie those linked to a song) - strSQL += "(SELECT song_artist.idArtist FROM song_artist)" - " OR artistview.idArtist IN "; - - // and always show any artists linked to an album (may be different from above due to album artist tag) - strSQL += "(SELECT album_artist.idArtist FROM album_artist)"; // Includes compliation albums hence "Various artists" + { // Artists can be only album artists, so for all artists (with linked albums or songs) + // we need to check both album_artist and song_artist tables. + // Role is determined from song_artist table, so even if looking for album artists only + // we can check those that have a specific role e.g. which album artist is a composer + // (but not neccessarily of songs in that album), from entries in the song_artist table. + // Role < -1 is used to indicate that all roles are wanted. + // When not album artists only and a specific role wanted then only the song_artist table is checked. + // When album artists only and role = 1 (an "artist") then only the album_artist table is checked. + if (albumArtistsOnly || idRole == 1) //Album artists only or no specific role, check album_artist table + strSQL += "(SELECT album_artist.idArtist FROM album_artist )"; // Includes compliation albums hence "Various artists" + if (albumArtistsOnly && idRole > 1) //Album artists only with role, check AND in song_artist + strSQL += " AND artistview.idArtist IN "; + if (!albumArtistsOnly && idRole == 1) //Song and album artists no specific role, check OR in song_artist + strSQL += " OR artistview.idArtist IN "; + if (!albumArtistsOnly || idRole > 1) //Song and album artists or album artists with specific role, check song_artist + strSQL += PrepareSQL("(SELECT song_artist.idArtist FROM song_artist WHERE song_artist.idRole = %i)", idRole); + + if (idRole < -1) //All artists contributing to songs, all roles. + strSQL = "(artistview.idArtist IN (SELECT song_artist.idArtist FROM song_artist) OR" + " artistview.idArtist IN (SELECT album_artist.idArtist FROM album_artist )"; } // remove the null string @@ -5819,32 +6116,26 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription option = options.find("artistid"); if (option != options.end()) { - int idArtist = static_cast(option->second.asInteger()); + int idArtist = (int)option->second.asInteger(); filter.AppendWhere(PrepareSQL( - "(EXISTS ( " - " SELECT 1 " - " FROM song " - " JOIN song_artist ON song.idSong = song_artist.idSong " - " WHERE song.idAlbum = albumview.idAlbum" - " AND song_artist.idArtist = %i " - ") OR " - "EXISTS ( " - " SELECT 1 " - " FROM album_artist " - " WHERE album_artist.idAlbum = albumview.idAlbum " - " AND album_artist.idArtist = %i " - "))", - idArtist, - idArtist - )); + "(EXISTS (SELECT 1 FROM song JOIN song_artist ON song.idSong = song_artist.idSong" + " WHERE song.idAlbum = albumview.idAlbum AND song_artist.idArtist = %i %s) OR " + "EXISTS (SELECT 1 FROM album_artist" + " WHERE album_artist.idAlbum = albumview.idAlbum AND album_artist.idArtist = %i))", + idArtist, strRoleSQL.c_str(), idArtist)); } else { option = options.find("artist"); if (option != options.end()) - filter.AppendWhere(PrepareSQL("albumview.idAlbum IN (SELECT song.idAlbum FROM song JOIN song_artist ON song.idSong = song_artist.idSong JOIN artist ON artist.idArtist = song_artist.idArtist WHERE artist.strArtist like '%s')" // All albums linked to this artist via songs - " OR albumview.idAlbum IN (SELECT album_artist.idAlbum FROM album_artist JOIN artist ON artist.idArtist = album_artist.idArtist WHERE artist.strArtist like '%s')", // All albums where album artists fit - option->second.asString().c_str(), option->second.asString().c_str())); + filter.AppendWhere(PrepareSQL( + "albumview.idAlbum IN (SELECT song.idAlbum FROM song JOIN song_artist ON song.idSong = song_artist.idSong " + "JOIN artist ON artist.idArtist = song_artist.idArtist " + "WHERE artist.strArtist like '%s' %s) OR " // All albums linked to this artist via songs + "albumview.idAlbum IN (SELECT album_artist.idAlbum FROM album_artist " + "JOIN artist ON artist.idArtist = album_artist.idArtist " + "WHERE artist.strArtist like '%s')", // All albums where album artists fit + option->second.asString().c_str(), strRoleSQL.c_str(), option->second.asString().c_str())); // no artist given, so exclude any single albums (aka empty tagged albums) else { @@ -5888,15 +6179,21 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription option = options.find("artistid"); if (option != options.end()) - filter.AppendWhere(PrepareSQL("songview.idSong IN (SELECT song_artist.idSong FROM song_artist WHERE song_artist.idArtist = %i)" // song artists - " OR songview.idSong IN (SELECT song.idSong FROM song JOIN album_artist ON song.idAlbum=album_artist.idAlbum WHERE album_artist.idArtist = %i)", // album artists - (int)option->second.asInteger(), (int)option->second.asInteger())); - + filter.AppendWhere(PrepareSQL( + "songview.idSong IN (SELECT song_artist.idSong FROM song_artist " + "WHERE song_artist.idArtist = %i %s) OR " // song artists + "songview.idSong IN (SELECT song.idSong FROM song JOIN album_artist ON song.idAlbum=album_artist.idAlbum " + "WHERE album_artist.idArtist = %i)", // album artists + (int)option->second.asInteger(), strRoleSQL.c_str(), (int)option->second.asInteger())); + option = options.find("artist"); if (option != options.end()) - filter.AppendWhere(PrepareSQL("songview.idSong IN (SELECT song_artist.idSong FROM song_artist JOIN artist ON artist.idArtist = song_artist.idArtist WHERE artist.strArtist like '%s')" // song artists - " OR songview.idSong IN (SELECT song.idSong FROM song JOIN album_artist ON song.idAlbum=album_artist.idAlbum JOIN artist ON artist.idArtist = album_artist.idArtist WHERE artist.strArtist like '%s')", // album artists - option->second.asString().c_str(), option->second.asString().c_str())); + filter.AppendWhere(PrepareSQL( + "songview.idSong IN (SELECT song_artist.idSong FROM song_artist JOIN artist ON artist.idArtist = song_artist.idArtist " + "WHERE artist.strArtist like '%s' %s) OR" // song artists + "songview.idSong IN (SELECT song.idSong FROM song JOIN album_artist ON song.idAlbum=album_artist.idAlbum " + "JOIN artist ON artist.idArtist = album_artist.idArtist WHERE artist.strArtist like '%s')", // album artists + option->second.asString().c_str(), strRoleSQL.c_str(), option->second.asString().c_str())); } option = options.find("xsp"); @@ -5911,7 +6208,17 @@ bool CMusicDatabase::GetFilter(CDbUrl &musicUrl, Filter &filter, SortDescription (xsp.GetGroup() == type && !xsp.IsGroupMixed())) { std::set playlists; - filter.AppendWhere(xsp.GetWhereClause(*this, playlists)); + std::string xspWhere = xsp.GetWhereClause(*this, playlists); + // When "artists" type playlist rules include role, need to avoid any conflict with default role = 1 filter clause. + // The song_artist clause is provided by the playlist and album artists only setting is over ruled + if (type == "artists" && xspWhere.find("song_artist.idRole = role.idRole") != xspWhere.npos) + { + filter.where.clear(); + // remove the null string and various artist entry + std::string strVariousArtists = g_localizeStrings.Get(340); + filter.AppendWhere(PrepareSQL("artistview.strArtist != '' AND artistview.strArtist <> '%s'", strVariousArtists.c_str())); + } + filter.AppendWhere(xspWhere); if (xsp.GetLimit() > 0) sorting.limitEnd = xsp.GetLimit(); diff --git a/xbmc/music/MusicDatabase.h b/xbmc/music/MusicDatabase.h index 5e4bfb430c9b1..a81233176ccf7 100644 --- a/xbmc/music/MusicDatabase.h +++ b/xbmc/music/MusicDatabase.h @@ -50,6 +50,7 @@ namespace dbiplus #define ERROR_REORG_SONGS 319 #define ERROR_REORG_ARTIST 321 #define ERROR_REORG_GENRE 323 +#define ERROR_REORG_ROLE 324 #define ERROR_REORG_PATH 325 #define ERROR_REORG_ALBUM 327 #define ERROR_WRITING_CHANGES 329 @@ -115,7 +116,7 @@ class CMusicDatabase : public CDatabase \param strComment [in] the ids of the added songs \param strMood [in] the mood of the added song \param strThumb [in] the ids of the added songs - \param artistString [in] the assembled artist string, denormalized from CONCAT(strArtist||strJoinPhrase) + \param artistString [in] the assembled artist string \param genres [in] a vector of genres to which this song belongs \param iTrack [in] the track number and disc number of the song \param iDuration [in] the duration of the song @@ -163,7 +164,7 @@ class CMusicDatabase : public CDatabase \param strComment [in] the ids of the added songs \param strMood [in] the mood of the added song \param strThumb [in] the ids of the added songs - \param artistString [in] the full artist string, denormalized from CONCAT(song_artist.strArtist || song_artist.strJoinPhrase) + \param artistString [in] the full artist string \param genres [in] a vector of genres to which this song belongs \param iTrack [in] the track number and disc number of the song \param iDuration [in] the duration of the song @@ -299,14 +300,19 @@ class CMusicDatabase : public CDatabase ///////////////////////////////////////////////// // Link tables ///////////////////////////////////////////////// - bool AddAlbumArtist(int idArtist, int idAlbum, std::string strArtist, std::string joinPhrase, bool featured, int iOrder); - bool GetAlbumsByArtist(int idArtist, bool includeFeatured, std::vector& albums); + bool AddAlbumArtist(int idArtist, int idAlbum, std::string strArtist, int iOrder); + bool GetAlbumsByArtist(int idArtist, std::vector& albums); bool GetArtistsByAlbum(int idAlbum, CFileItem* item); bool DeleteAlbumArtistsByAlbum(int idAlbum); - bool AddSongArtist(int idArtist, int idSong, std::string strArtist, std::string joinPhrase, bool featured, int iOrder); - bool GetSongsByArtist(int idArtist, bool includeFeatured, std::vector& songs); - bool GetArtistsBySong(int idSong, bool includeFeatured, std::vector& artists); + int AddRole(const std::string &strRole); + bool AddSongArtist(int idArtist, int idSong, const std::string& strRole, const std::string& strArtist, int iOrder); + bool AddSongArtist(int idArtist, int idSong, int idRole, const std::string& strArtist, int iOrder); + int AddSongContributor(int idSong, const std::string& strRole, const std::string& strArtist); + void AddSongContributors(int idSong, const VECMUSICROLES& contributors); + int GetRoleByName(const std::string& strRole); + bool GetSongsByArtist(int idArtist, std::vector& songs); + bool GetArtistsBySong(int idSong, std::vector& artists); bool DeleteSongArtistsBySong(int idSong); bool AddSongGenre(int idGenre, int idSong, int iOrder); @@ -340,6 +346,9 @@ class CMusicDatabase : public CDatabase int GetCompilationAlbumsCount(); int GetSinglesCount(); + + int GetArtistCountForRole(int role); + int GetArtistCountForRole(const std::string& strRole); /*! \brief Increment the playcount of an item Increments the playcount and updates the last played date @@ -353,6 +362,7 @@ class CMusicDatabase : public CDatabase ///////////////////////////////////////////////// bool GetGenresNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter(), bool countOnly = false); bool GetYearsNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter()); + bool GetRolesNav(const std::string& strBaseDir, CFileItemList& items, const Filter &filter = Filter()); bool GetArtistsNav(const std::string& strBaseDir, CFileItemList& items, bool albumArtistsOnly = false, int idGenre = -1, int idAlbum = -1, int idSong = -1, const Filter &filter = Filter(), const SortDescription &sortDescription = SortDescription(), bool countOnly = false); bool GetCommonNav(const std::string &strBaseDir, const std::string &table, const std::string &labelField, CFileItemList &items, const Filter &filter /* = Filter() */, bool countOnly /* = false */); bool GetAlbumTypesNav(const std::string &strBaseDir, CFileItemList &items, const Filter &filter = Filter(), bool countOnly = false); @@ -495,6 +505,7 @@ class CMusicDatabase : public CDatabase CAlbum GetAlbumFromDataset(dbiplus::Dataset* pDS, int offset = 0, bool imageURL = false); CAlbum GetAlbumFromDataset(const dbiplus::sql_record* const record, int offset = 0, bool imageURL = false); CArtistCredit GetArtistCreditFromDataset(const dbiplus::sql_record* const record, int offset = 0); + CMusicRole GetArtistRoleFromDataset(const dbiplus::sql_record* const record, int offset = 0); /*! \brief Updates the dateAdded field in the song table for the file with the given songId and the given path based on the files modification date \param songId id of the song in the song table @@ -511,6 +522,7 @@ class CMusicDatabase : public CDatabase bool CleanupAlbums(); bool CleanupArtists(); bool CleanupGenres(); + bool CleanupRoles(); virtual void UpdateTables(int version); bool SearchArtists(const std::string& search, CFileItemList &artists); bool SearchAlbums(const std::string& search, CFileItemList &albums); @@ -582,10 +594,10 @@ class CMusicDatabase : public CDatabase // used for GetAlbum to get the cascaded album/song artist credits artistCredit_idEntity = 0, // can be idSong or idAlbum depending on context artistCredit_idArtist, + artistCredit_idRole, + artistCredit_strRole, artistCredit_strArtist, artistCredit_strMusicBrainzArtistID, - artistCredit_bFeatured, - artistCredit_strJoinPhrase, artistCredit_iOrder, artistCredit_enumCount } ArtistCreditFields; diff --git a/xbmc/music/Song.cpp b/xbmc/music/Song.cpp index 6b5d74355e1af..9ddfbe25aa5a8 100644 --- a/xbmc/music/Song.cpp +++ b/xbmc/music/Song.cpp @@ -54,8 +54,7 @@ CSong::CSong(CFileItem& item) artistName = (i < artist.size()) ? artist[i] : artist[0]; if (artistName.empty()) artistName = artistId; - std::string strJoinPhrase = (i == tag.GetMusicBrainzArtistID().size()-1) ? "" : g_advancedSettings.m_musicItemSeparator; - CArtistCredit artistCredit(artistName, artistId, strJoinPhrase); + CArtistCredit artistCredit(artistName, artistId); artistCredits.push_back(artistCredit); } } @@ -63,14 +62,14 @@ CSong::CSong(CFileItem& item) { // no musicbrainz info, so fill in directly for (std::vector::const_iterator it = tag.GetArtist().begin(); it != tag.GetArtist().end(); ++it) { - std::string strJoinPhrase = (it == --tag.GetArtist().end() ? "" : g_advancedSettings.m_musicItemSeparator); - CArtistCredit artistCredit(*it, "", strJoinPhrase); + CArtistCredit artistCredit(*it); artistCredits.push_back(artistCredit); } } strAlbum = tag.GetAlbum(); m_albumArtist = tag.GetAlbumArtist(); strMusicBrainzTrackID = tag.GetMusicBrainzTrackID(); + m_musicRoles = tag.GetContributors(); strComment = tag.GetComment(); strCueSheet = tag.GetCueSheet(); strMood = tag.GetMood(); @@ -142,6 +141,7 @@ void CSong::Clear() genre.clear(); strThumb.clear(); strMusicBrainzTrackID.clear(); + m_musicRoles.clear(); strComment.clear(); strMood.clear(); rating = 0; @@ -168,6 +168,11 @@ const std::vector CSong::GetArtist() const { songartists.push_back(artistCredit->GetArtist()); } + //When artist credits have not been populated attempt to build an artist vector from the descrpition string + //This is a tempory 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, g_advancedSettings.m_musicItemSeparator); return songartists; } @@ -188,9 +193,12 @@ const std::string CSong::GetArtistString() const //but is takes precidence as a string because artistcredits is not always filled during processing if (!strArtistDesc.empty()) return strArtistDesc; + std::vector artistvector; + for (VECARTISTCREDITS::const_iterator i = artistCredits.begin(); i != artistCredits.end(); ++i) + artistvector.push_back(i->GetArtist()); std::string artistString; - for (VECARTISTCREDITS::const_iterator artistCredit = artistCredits.begin(); artistCredit != artistCredits.end(); ++artistCredit) - artistString += artistCredit->GetArtist() + artistCredit->GetJoinPhrase(); + if (!artistvector.empty()) + artistString = StringUtils::Join(artistvector, g_advancedSettings.m_musicItemSeparator); return artistString; } @@ -203,6 +211,11 @@ const std::vector CSong::GetArtistIDArray() const return artistids; } +void CSong::AppendArtistRole(const CMusicRole& musicRole) +{ + m_musicRoles.push_back(musicRole); +} + bool CSong::HasArt() const { if (!strThumb.empty()) return true; diff --git a/xbmc/music/Song.h b/xbmc/music/Song.h index 4e1d56386de2b..aeb68446a6024 100644 --- a/xbmc/music/Song.h +++ b/xbmc/music/Song.h @@ -99,6 +99,15 @@ class CSong: public ISerializable \return album artist names as a vector of strings */ const std::vector GetAlbumArtist() const { return m_albumArtist; } + + /*! \brief Get the full list of artist names and the role each played for those + that contributed to the recording. Given in music file tags other than ARTIST + or ALBUMARTIST, e.g. COMPOSER or CONDUCTOR etc. + \return a vector of all contributing artist names and their roles + */ + const VECMUSICROLES& GetContributors() const { return m_musicRoles; }; + //void AddArtistRole(const int &role, const std::string &artist); + void AppendArtistRole(const CMusicRole& musicRole); /*! \brief Set album artist vector. Album artist is held local to song until album created for inital processing only. @@ -113,6 +122,11 @@ class CSong: public ISerializable */ bool HasArtistCredits() const { return !artistCredits.empty(); } + /*! \brief Whether this song has any artists in music roles (contributors) vector + Tests if contributors has been populated yet, there may be none. + */ + bool HasContributors() const { return !m_musicRoles.empty(); } + /*! \brief whether this song has art associated with it Tests both the strThumb and embeddedArt members. */ @@ -151,9 +165,9 @@ class CSong: public ISerializable bool bCompilation; ReplayGain replayGain; - private: std::vector m_albumArtist; // Album artist from tag for album processing, no desc or MBID + VECMUSICROLES m_musicRoles; }; /*! diff --git a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp index abbb673b2bdb7..88b738fb8fe8b 100644 --- a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp +++ b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp @@ -253,7 +253,7 @@ void CGUIDialogMusicInfo::SetDiscography() database.Open(); std::vector albumsByArtist; - database.GetAlbumsByArtist(m_artist.idArtist, true, albumsByArtist); + database.GetAlbumsByArtist(m_artist.idArtist, albumsByArtist); for (unsigned int i=0;i artists; CVariant artistthumbs; - db.GetArtistsBySong(item->GetMusicInfoTag()->GetDatabaseId(), true, artists); + db.GetArtistsBySong(item->GetMusicInfoTag()->GetDatabaseId(), artists); for (std::vector::const_iterator artistId = artists.begin(); artistId != artists.end(); ++artistId) { std::string thumb = db.GetArtForItem(*artistId, MediaTypeArtist, "thumb"); diff --git a/xbmc/music/infoscanner/MusicInfoScanner.cpp b/xbmc/music/infoscanner/MusicInfoScanner.cpp index 1dc122d820d1c..a4357f5360210 100644 --- a/xbmc/music/infoscanner/MusicInfoScanner.cpp +++ b/xbmc/music/infoscanner/MusicInfoScanner.cpp @@ -714,8 +714,7 @@ void CMusicInfoScanner::FileItemsToAlbums(CFileItemList& items, VECALBUMS& album album.strAlbum = songsByAlbumName->first; for (std::vector::iterator it = common.begin(); it != common.end(); ++it) { - std::string strJoinPhrase = (it == --common.end() ? "" : g_advancedSettings.m_musicItemSeparator); - CArtistCredit artistCredit(*it, strJoinPhrase); + CArtistCredit artistCredit(*it); album.artistCredits.push_back(artistCredit); } album.bCompilation = compilation; diff --git a/xbmc/music/tags/MusicInfoTag.cpp b/xbmc/music/tags/MusicInfoTag.cpp index ccc73cb4bcd93..0db641f404e29 100644 --- a/xbmc/music/tags/MusicInfoTag.cpp +++ b/xbmc/music/tags/MusicInfoTag.cpp @@ -24,6 +24,7 @@ #include "music/Album.h" #include "music/Artist.h" #include "utils/StringUtils.h" +#include "guilib/LocalizeStrings.h" #include "settings/AdvancedSettings.h" #include "utils/Variant.h" #include "utils/Archive.h" @@ -116,6 +117,7 @@ const CMusicInfoTag& CMusicInfoTag::operator =(const CMusicInfoTag& tag) m_musicBrainzAlbumArtistID = tag.m_musicBrainzAlbumArtistID; m_musicBrainzAlbumArtistHints = tag.m_musicBrainzAlbumArtistHints; m_strMusicBrainzTRMID = tag.m_strMusicBrainzTRMID; + m_musicRoles = tag.m_musicRoles; m_strComment = tag.m_strComment; m_strMood = tag.m_strMood; m_strLyrics = tag.m_strLyrics; @@ -137,7 +139,7 @@ const CMusicInfoTag& CMusicInfoTag::operator =(const CMusicInfoTag& tag) m_replayGain = tag.m_replayGain; m_albumReleaseType = tag.m_albumReleaseType; - memcpy(&m_dwReleaseDate, &tag.m_dwReleaseDate, sizeof(m_dwReleaseDate) ); + memcpy(&m_dwReleaseDate, &tag.m_dwReleaseDate, sizeof(m_dwReleaseDate)); m_coverArt = tag.m_coverArt; return *this; } @@ -235,7 +237,7 @@ const std::vector& CMusicInfoTag::GetGenre() const void CMusicInfoTag::GetReleaseDate(SYSTEMTIME& dateTime) const { - memcpy(&dateTime, &m_dwReleaseDate, sizeof(m_dwReleaseDate) ); + memcpy(&dateTime, &m_dwReleaseDate, sizeof(m_dwReleaseDate)); } int CMusicInfoTag::GetYear() const @@ -582,7 +584,7 @@ const std::string& CMusicInfoTag::GetMusicBrainzTRMID() const void CMusicInfoTag::SetMusicBrainzTrackID(const std::string& strTrackID) { - m_strMusicBrainzTrackID=strTrackID; + m_strMusicBrainzTrackID = strTrackID; } void CMusicInfoTag::SetMusicBrainzArtistID(const std::vector& musicBrainzArtistId) @@ -597,7 +599,7 @@ void CMusicInfoTag::SetMusicBrainzArtistHints(const std::vector& mu void CMusicInfoTag::SetMusicBrainzAlbumID(const std::string& strAlbumID) { - m_strMusicBrainzAlbumID=strAlbumID; + m_strMusicBrainzAlbumID = strAlbumID; } void CMusicInfoTag::SetMusicBrainzAlbumArtistID(const std::vector& musicBrainzAlbumArtistId) @@ -613,7 +615,7 @@ void CMusicInfoTag::SetMusicBrainzAlbumArtistHints(const std::vectorGetRoleId(); + ar << credit->GetRoleDesc(); + ar << credit->GetArtist(); + ar << credit->GetArtistId(); + } ar << m_strMood; ar << m_Rating; ar << m_Userrating; @@ -868,6 +894,21 @@ void CMusicInfoTag::Archive(CArchive& ar) ar >> m_lastPlayed; ar >> m_dateAdded; ar >> m_strComment; + int iMusicRolesSize; + ar >> iMusicRolesSize; + m_musicRoles.reserve(iMusicRolesSize); + for (int i = 0; i < iMusicRolesSize; ++i) + { + int idRole; + long idArtist; + std::string strArtist; + std::string strRole; + ar >> idRole; + ar >> strRole; + ar >> strArtist; + ar >> idArtist; + m_musicRoles.emplace_back(idRole, strRole, strArtist, idArtist); + } ar >> m_strMood; ar >> m_Rating; ar >> m_Userrating; @@ -901,6 +942,7 @@ void CMusicInfoTag::Clear() m_strMusicBrainzAlbumID.clear(); m_musicBrainzAlbumArtistID.clear(); m_strMusicBrainzTRMID.clear(); + m_musicRoles.clear(); m_iDuration = 0; m_iTrack = 0; m_bLoaded = false; @@ -913,7 +955,7 @@ void CMusicInfoTag::Clear() m_iDbId = -1; m_type.clear(); m_iTimesPlayed = 0; - memset(&m_dwReleaseDate, 0, sizeof(m_dwReleaseDate) ); + memset(&m_dwReleaseDate, 0, sizeof(m_dwReleaseDate)); m_iAlbumId = -1; m_coverArt.clear(); m_replayGain = ReplayGain(); @@ -957,6 +999,72 @@ void CMusicInfoTag::AppendGenre(const std::string &genre) m_genre.push_back(genre); } +void CMusicInfoTag::AddArtistRole(const std::string& Role, const std::string& strArtist) +{ + if (!strArtist.empty()) + AddArtistRole(Role, StringUtils::Split(strArtist, g_advancedSettings.m_musicItemSeparator)); +} + +void CMusicInfoTag::AddArtistRole(const std::string& Role, const std::vector& artists) +{ + for (unsigned int index = 0; index < artists.size(); index++) + { + CMusicRole ArtistCredit(Role, Trim(artists.at(index))); + //Prevent duplicate entries + VECMUSICROLES::iterator credit = find(m_musicRoles.begin(), m_musicRoles.end(), ArtistCredit); + if (credit == m_musicRoles.end()) + m_musicRoles.push_back(ArtistCredit); + } +} + +void CMusicInfoTag::AppendArtistRole(const CMusicRole& ArtistRole) +{ + //Append contributor, no check for duplicates as from database + m_musicRoles.push_back(ArtistRole); +} + +const std::string CMusicInfoTag::GetArtistStringForRole(const std::string& strRole) const +{ + std::vector artistvector; + for (VECMUSICROLES::const_iterator credit = m_musicRoles.begin(); credit != m_musicRoles.end(); ++credit) + { + if (StringUtils::EqualsNoCase(credit->GetRoleDesc(), strRole)) + artistvector.push_back(credit->GetArtist()); + } + return StringUtils::Join(artistvector, g_advancedSettings.m_musicItemSeparator); +} + +const std::string CMusicInfoTag::GetContributorsText() const +{ + std::string strLabel; + for (VECMUSICROLES::const_iterator credit = m_musicRoles.begin(); credit != m_musicRoles.end(); ++credit) + { + strLabel += StringUtils::Format("%s\n", credit->GetArtist().c_str()); + } + return StringUtils::TrimRight(strLabel, "\n"); +} + +const std::string CMusicInfoTag::GetContributorsAndRolesText() const +{ + std::string strLabel; + for (VECMUSICROLES::const_iterator credit = m_musicRoles.begin(); credit != m_musicRoles.end(); ++credit) + { + strLabel += StringUtils::Format("%s - %s\n", credit->GetRoleDesc().c_str(), credit->GetArtist().c_str()); + } + return StringUtils::TrimRight(strLabel, "\n"); +} + + +const VECMUSICROLES &CMusicInfoTag::GetContributors() const +{ + return m_musicRoles; +} + +void CMusicInfoTag::SetContributors(const VECMUSICROLES& contributors) +{ + m_musicRoles = contributors; +} + std::string CMusicInfoTag::Trim(const std::string &value) const { std::string trimmedValue(value); diff --git a/xbmc/music/tags/MusicInfoTag.h b/xbmc/music/tags/MusicInfoTag.h index b87cd0f1deac6..9f9b629a20c3e 100644 --- a/xbmc/music/tags/MusicInfoTag.h +++ b/xbmc/music/tags/MusicInfoTag.h @@ -30,6 +30,7 @@ class CVariant; #include "ReplayGain.h" #include "XBDateTime.h" #include "music/Album.h" +#include "music/Artist.h" #include "music/EmbeddedArt.h" #include "utils/IArchivable.h" #include "utils/ISerializable.h" @@ -111,7 +112,7 @@ class CMusicInfoTag : public IArchivable, public ISerializable, public ISortable void SetLoaded(bool bOnOff = true); void SetArtist(const CArtist& artist); void SetAlbum(const CAlbum& album); - void SetSong(const CSong& song); + void SetSong(const CSong& song); void SetMusicBrainzTrackID(const std::string& strTrackID); void SetMusicBrainzArtistID(const std::vector& musicBrainzArtistId); void SetMusicBrainzArtistHints(const std::vector& musicBrainzArtistHints); @@ -154,12 +155,23 @@ class CMusicInfoTag : public IArchivable, public ISerializable, public ISortable \param genre genre to add. */ void AppendGenre(const std::string &genre); + + void AddArtistRole(const std::string& Role, const std::string& strArtist); + void AddArtistRole(const std::string& Role, const std::vector& artists); + void AppendArtistRole(const CMusicRole& ArtistRole); + const std::string GetArtistStringForRole(const std::string& strRole) const; + const std::string GetContributorsText() const; + const std::string GetContributorsAndRolesText() const; + const VECMUSICROLES &GetContributors() const; + void SetContributors(const VECMUSICROLES& contributors); + bool HasContributors() const { return !m_musicRoles.empty(); } virtual void Archive(CArchive& ar); virtual void Serialize(CVariant& ar) const; virtual void ToSortable(SortItem& sortable, Field field) const; void Clear(); + protected: /*! \brief Trim whitespace off the given string \param value string to trim @@ -182,6 +194,7 @@ class CMusicInfoTag : public IArchivable, public ISerializable, public ISortable std::vector m_musicBrainzAlbumArtistID; std::vector m_musicBrainzAlbumArtistHints; std::string m_strMusicBrainzTRMID; + VECMUSICROLES m_musicRoles; //Artists contributing to the recording and role (from tags other than ARTIST or ALBUMARTIST) std::string m_strComment; std::string m_strMood; std::string m_strLyrics; diff --git a/xbmc/music/tags/TagLoaderTagLib.cpp b/xbmc/music/tags/TagLoaderTagLib.cpp index 02c169148b4fa..e082668df4bd5 100644 --- a/xbmc/music/tags/TagLoaderTagLib.cpp +++ b/xbmc/music/tags/TagLoaderTagLib.cpp @@ -124,6 +124,25 @@ bool CTagLoaderTagLib::ParseTag(ASF::Tag *asf, EmbeddedArt *art, CMusicInfoTag& SetGenre(tag, GetASFStringList(it->second)); else if (it->first == "WM/Mood") tag.SetMood(it->second.front().toString().to8Bit(true)); + else if (it->first == "WM/Composer") + AddArtistRole(tag, "Composer", GetASFStringList(it->second)); + else if (it->first == "WM/Conductor") + AddArtistRole(tag, "Conductor", GetASFStringList(it->second)); + //No ASF/WMA tag from Taglib for "ensemble" + else if (it->first == "WM/Writer") + AddArtistRole(tag, "Lyricist", GetASFStringList(it->second)); + else if (it->first == "WM/ModifiedBy") + AddArtistRole(tag, "Remixer", GetASFStringList(it->second)); + else if (it->first == "WM/Engineer") + AddArtistRole(tag, "Engineer", GetASFStringList(it->second)); + else if (it->first == "WM/Producer") + AddArtistRole(tag, "Producer", GetASFStringList(it->second)); + else if (it->first == "WM/DJMixer") + AddArtistRole(tag, "DJMixer", GetASFStringList(it->second)); + else if (it->first == "WM/Mixer") + AddArtistRole(tag, "mixer", GetASFStringList(it->second)); + else if (it->first == "WM/Publisher") + {} // Known unsupported, supress warnings else if (it->first == "WM/AlbumArtistSortOrder") {} // Known unsupported, supress warnings else if (it->first == "WM/ArtistSortOrder") @@ -241,6 +260,11 @@ bool CTagLoaderTagLib::ParseTag(ID3v2::Tag *id3v2, MUSIC_INFO::EmbeddedArt *art, else if (it->first == "TYER") tag.SetYear(strtol(it->second.front()->toString().toCString(true), NULL, 10)); else if (it->first == "TCMP") tag.SetCompilation((strtol(it->second.front()->toString().toCString(true), NULL, 10) == 0) ? false : true); else if (it->first == "TENC") {} // EncodedBy + else if (it->first == "TCOM") AddArtistRole(tag, "Composer", GetID3v2StringList(it->second)); + else if (it->first == "TPE3") AddArtistRole(tag, "Conductor", GetID3v2StringList(it->second)); + else if (it->first == "TEXT") AddArtistRole(tag, "Lyricist", GetID3v2StringList(it->second)); + else if (it->first == "TPE4") AddArtistRole(tag, "Remixer", GetID3v2StringList(it->second)); + else if (it->first == "TPUB") {} // Publisher. Known unsupported, supress warnings else if (it->first == "TCOP") {} // Copyright message else if (it->first == "TDRC") tag.SetYear(strtol(it->second.front()->toString().toCString(true), NULL, 10)); else if (it->first == "TDRL") tag.SetYear(strtol(it->second.front()->toString().toCString(true), NULL, 10)); @@ -302,6 +326,26 @@ bool CTagLoaderTagLib::ParseTag(ID3v2::Tag *id3v2, MUSIC_INFO::EmbeddedArt *art, else if (g_advancedSettings.m_logLevel == LOG_LEVEL_MAX) CLog::Log(LOGDEBUG, "unrecognized user text tag detected: TXXX:%s", frame->description().toCString(true)); } + else if (it->first == "TIPL") + // Loop through and process the involved people list + // For example Arranger, Engineer, Producer, DJMixer or Mixer + // In fieldlist every odd field is a function, and every even is an artist or a comma delimited list of artists. + for (ID3v2::FrameList::ConstIterator ip = it->second.begin(); ip != it->second.end(); ++ip) + { + ID3v2::TextIdentificationFrame *tiplframe = dynamic_cast (*ip); + if (tiplframe) + AddArtistRole(tag, StringListToVectorString(tiplframe->fieldList())); + } + else if (it->first == "TMCL") + // Loop through and process the musicain credits list + // It is a mapping between the instrument and the person that played it, but also includes "orchestra" or "soloist". + // In fieldlist every odd field is an instrument, and every even is an artist or a comma delimited list of artists. + for (ID3v2::FrameList::ConstIterator ip = it->second.begin(); ip != it->second.end(); ++ip) + { + ID3v2::TextIdentificationFrame *tiplframe = dynamic_cast (*ip); + if (tiplframe) + AddArtistRole(tag, StringListToVectorString(tiplframe->fieldList())); + } else if (it->first == "UFID") // Loop through any UFID frames and set them for (ID3v2::FrameList::ConstIterator ut = it->second.begin(); ut != it->second.end(); ++ut) @@ -411,6 +455,31 @@ bool CTagLoaderTagLib::ParseTag(APE::Tag *ape, EmbeddedArt *art, CMusicInfoTag& tag.SetCueSheet(it->second.toString().to8Bit(true)); else if (it->first == "ENCODEDBY") {} + else if (it->first == "COMPOSER") + AddArtistRole(tag, "Composer", StringListToVectorString(it->second.toStringList())); + else if (it->first == "CONDUCTOR") + AddArtistRole(tag, "Conductor", StringListToVectorString(it->second.toStringList())); + else if ((it->first == "BAND") || (it->first == "ENSEMBLE")) + AddArtistRole(tag, "Orchestra", StringListToVectorString(it->second.toStringList())); + else if (it->first == "LYRICIST") + AddArtistRole(tag, "Lyricist", StringListToVectorString(it->second.toStringList())); + else if (it->first == "MIXARTIST") + AddArtistRole(tag, "Remixer", StringListToVectorString(it->second.toStringList())); + else if (it->first == "ARRANGER") + AddArtistRole(tag, "Arranger", StringListToVectorString(it->second.toStringList())); + else if (it->first == "ENGINEER") + AddArtistRole(tag, "Engineer", StringListToVectorString(it->second.toStringList())); + else if (it->first == "PRODUCER") + AddArtistRole(tag, "Producer", StringListToVectorString(it->second.toStringList())); + else if (it->first == "DJMIXER") + AddArtistRole(tag, "DJMixer", StringListToVectorString(it->second.toStringList())); + else if (it->first == "MIXER") + AddArtistRole(tag, "Mixer", StringListToVectorString(it->second.toStringList())); + else if (it->first == "PERFORMER") + // Picard uses PERFORMER tag as musician credits list formatted "name (instrument)" + AddArtistInstrument(tag, StringListToVectorString(it->second.toStringList())); + else if (it->first == "LABEL") + {} // Publisher. Known unsupported, supress warnings else if (it->first == "COMPILATION") tag.SetCompilation(it->second.toString().toInt() == 1); else if (it->first == "LYRICS") @@ -456,11 +525,11 @@ bool CTagLoaderTagLib::ParseTag(Ogg::XiphComment *xiph, EmbeddedArt *art, CMusic if (it->first == "ARTIST") SetArtist(tag, StringListToVectorString(it->second)); else if (it->first == "ARTISTS") - tag.SetMusicBrainzArtistHints(StringListToVectorString(it->second)); + SetArtistHints(tag, StringListToVectorString(it->second)); else if (it->first == "ALBUMARTIST" || it->first == "ALBUM ARTIST") SetAlbumArtist(tag, StringListToVectorString(it->second)); else if (it->first == "ALBUMARTISTS" || it->first == "ALBUM ARTISTS") - tag.SetMusicBrainzAlbumArtistHints(StringListToVectorString(it->second)); + SetAlbumArtistHints(tag, StringListToVectorString(it->second)); else if (it->first == "ALBUM") tag.SetAlbum(it->second.front().to8Bit(true)); else if (it->first == "TITLE") @@ -480,9 +549,32 @@ bool CTagLoaderTagLib::ParseTag(Ogg::XiphComment *xiph, EmbeddedArt *art, CMusic else if (it->first == "CUESHEET") tag.SetCueSheet(it->second.front().to8Bit(true)); else if (it->first == "ENCODEDBY") - {} - else if (it->first == "ENSEMBLE") - {} + {} // Known but unsupported, supress warnings + else if (it->first == "COMPOSER") + AddArtistRole(tag, "Composer", StringListToVectorString(it->second)); + else if (it->first == "CONDUCTOR") + AddArtistRole(tag, "Conductor", StringListToVectorString(it->second)); + else if ((it->first == "BAND") || (it->first == "ENSEMBLE")) + AddArtistRole(tag, "Orchestra", StringListToVectorString(it->second)); + else if (it->first == "LYRICIST") + AddArtistRole(tag, "Lyricist", StringListToVectorString(it->second)); + else if (it->first == "MIXARTIST") + AddArtistRole(tag, "Remixer", StringListToVectorString(it->second)); + else if (it->first == "ARRANGER") + AddArtistRole(tag, "Arranger", StringListToVectorString(it->second)); + else if (it->first == "ENGINEER") + AddArtistRole(tag, "Engineer", StringListToVectorString(it->second)); + else if (it->first == "PRODUCER") + AddArtistRole(tag, "Producer", StringListToVectorString(it->second)); + else if (it->first == "DJMIXER") + AddArtistRole(tag, "DJMixer", StringListToVectorString(it->second)); + else if (it->first == "MIXER") + AddArtistRole(tag, "Mixer", StringListToVectorString(it->second)); + else if (it->first == "PERFORMER") + // Picard uses PERFORMER tag as musician credits list formatted "name (instrument)" + AddArtistInstrument(tag, StringListToVectorString(it->second)); + else if (it->first == "LABEL") + {} // Publisher. Known unsupported, supress warnings else if (it->first == "COMPILATION") tag.SetCompilation(it->second.front().toInt() == 1); else if (it->first == "LYRICS") @@ -594,6 +686,26 @@ bool CTagLoaderTagLib::ParseTag(MP4::Tag *mp4, EmbeddedArt *art, CMusicInfoTag& SetGenre(tag, StringListToVectorString(it->second.toStringList())); else if (it->first == "\251cmt") tag.SetComment(it->second.toStringList().front().to8Bit(true)); + else if (it->first == "\251wrt") + AddArtistRole(tag, "Composer", StringListToVectorString(it->second.toStringList())); + else if (it->first == "----:com.apple.iTunes:CONDUCTOR") + AddArtistRole(tag, "Conductor", StringListToVectorString(it->second.toStringList())); + //No MP4 standard tag for "ensemble" + else if (it->first == "----:com.apple.iTunes:LYRICIST") + AddArtistRole(tag, "Lyricist", StringListToVectorString(it->second.toStringList())); + else if (it->first == "----:com.apple.iTunes:REMIXER") + AddArtistRole(tag, "Remixer", StringListToVectorString(it->second.toStringList())); + else if (it->first == "----:com.apple.iTunes:ENGINEER") + AddArtistRole(tag, "Engineer", StringListToVectorString(it->second.toStringList())); + else if (it->first == "----:com.apple.iTunes:PRODUCER") + AddArtistRole(tag, "Producer", StringListToVectorString(it->second.toStringList())); + else if (it->first == "----:com.apple.iTunes:DJMIXER") + AddArtistRole(tag, "DJMixer", StringListToVectorString(it->second.toStringList())); + else if (it->first == "----:com.apple.iTunes:MIXER") + AddArtistRole(tag, "Mixer", StringListToVectorString(it->second.toStringList())); + //No MP4 standard tag for musician credits + else if (it->first == "----:com.apple.iTunes:LABEL") + {} // Publisher. Known unsupported, supress warnings else if (it->first == "cpil") tag.SetCompilation(it->second.toBool()); else if (it->first == "trkn") @@ -803,6 +915,52 @@ void CTagLoaderTagLib::SetGenre(CMusicInfoTag &tag, const std::vector &values) +{ + if (values.size() == 1) + tag.AddArtistRole(strRole, values[0]); + else + tag.AddArtistRole(strRole, values); +} + +void CTagLoaderTagLib::AddArtistRole(CMusicInfoTag &tag, const std::vector &values) +{ + // Values contains role, name pairs (as in ID3 standard for TIPL or TMCL tags) + // Every odd entry is a function (e.g. Producer, Arranger etc.) or instrument (e.g. Orchestra, Vocal, Piano) + // and every even is an artist or a comma delimited list of artists. + + if (values.size() % 2 != 0) // Must contain an even number of entries + return; + + for (int i = 0; i < values.size() - 1; i += 2) + tag.AddArtistRole(values[i], StringUtils::Split(values[i + 1], ",")); +} + +void CTagLoaderTagLib::AddArtistInstrument(CMusicInfoTag &tag, const std::vector &values) +{ + // Values is a musician credits list, each entry is artist name followed by instrument (or function) + // e.g. violin, drums, background vocals, solo, orchestra etc. in brackets. This is how Picard uses PERFORMER tag. + // If there is not a pair of brackets then role is "performer" by default, and the whole entry is + // taken as artist name. + + for (int i = 0; i < values.size(); ++i) + { + std::string strRole = "Performer"; + std::string strArtist = values[i]; + unsigned firstLim = values[i].find_first_of("("); + unsigned lastLim = values[i].find_last_of(")"); + if (lastLim != std::string::npos && firstLim != std::string::npos && firstLim < lastLim - 1) + { + //Pair of brackets with something between them + strRole = values[i].substr(firstLim + 1, lastLim - firstLim - 1); + StringUtils::Trim(strRole); + strArtist.erase(firstLim, lastLim - firstLim + 1); + } + StringUtils::Trim(strArtist); + tag.AddArtistRole(strRole, strArtist); + } +} + bool CTagLoaderTagLib::Load(const std::string& strFileName, CMusicInfoTag& tag, const std::string& fallbackFileExtension, MUSIC_INFO::EmbeddedArt *art /* = NULL */) { std::string strExtension = URIUtils::GetExtension(strFileName); diff --git a/xbmc/music/tags/TagLoaderTagLib.h b/xbmc/music/tags/TagLoaderTagLib.h index 42e5467d82789..c7b0c7e92e20d 100644 --- a/xbmc/music/tags/TagLoaderTagLib.h +++ b/xbmc/music/tags/TagLoaderTagLib.h @@ -65,6 +65,9 @@ class CTagLoaderTagLib : public MUSIC_INFO::IMusicInfoTagLoader static void SetAlbumArtist(MUSIC_INFO::CMusicInfoTag &tag, const std::vector &values); static void SetAlbumArtistHints(MUSIC_INFO::CMusicInfoTag &tag, const std::vector &values); static void SetGenre(MUSIC_INFO::CMusicInfoTag &tag, const std::vector &values); + static void AddArtistRole(MUSIC_INFO::CMusicInfoTag &tag, const std::string& strRole, const std::vector &values); + static void AddArtistRole(MUSIC_INFO::CMusicInfoTag &tag, const std::vector &values); + static void AddArtistInstrument(MUSIC_INFO::CMusicInfoTag &tag, const std::vector &values); static int POPMtoXBMC(int popm); template diff --git a/xbmc/playlists/SmartPlayList.cpp b/xbmc/playlists/SmartPlayList.cpp index 189a40d76a4b6..e190a1f40ddcf 100644 --- a/xbmc/playlists/SmartPlayList.cpp +++ b/xbmc/playlists/SmartPlayList.cpp @@ -60,6 +60,7 @@ static const translateField fields[] = { { "albumartist", FieldAlbumArtist, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 566 }, { "artist", FieldArtist, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 557 }, { "tracknumber", FieldTrackNumber, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 554 }, + { "role", FieldRole, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 38033 }, { "comment", FieldComment, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 569 }, { "review", FieldReview, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 183 }, { "themes", FieldThemes, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21895 }, @@ -329,6 +330,7 @@ std::vector CSmartPlaylistRule::GetFields(const std::string &type) fields.push_back(FieldRating); fields.push_back(FieldUserRating); fields.push_back(FieldPlaycount); + fields.push_back(FieldPath); } else if (type == "artists") { @@ -342,6 +344,7 @@ std::vector CSmartPlaylistRule::GetFields(const std::string &type) fields.push_back(FieldBandFormed); fields.push_back(FieldDisbanded); fields.push_back(FieldDied); + fields.push_back(FieldRole); } else if (type == "tvshows") { @@ -776,6 +779,8 @@ std::string CSmartPlaylistRule::FormatWhereClause(const std::string &negate, con query = negate + " EXISTS (SELECT 1 FROM song, song_artist, artist WHERE song.idAlbum = " + GetField(FieldId, strType) + " AND song.idSong = song_artist.idSong AND song_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")"; else if (m_field == FieldAlbumArtist) query = negate + " EXISTS (SELECT 1 FROM album_artist, artist WHERE album_artist.idAlbum = " + GetField(FieldId, strType) + " AND album_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")"; + else if (m_field == FieldPath) + query = negate + " EXISTS (SELECT 1 FROM song JOIN path on song.idpath = path.idpath WHERE song.idAlbum = " + GetField(FieldId, strType) + " AND path.strPath" + parameter + ")"; } else if (strType == "artists") { @@ -787,6 +792,10 @@ std::string CSmartPlaylistRule::FormatWhereClause(const std::string &negate, con query += " OR "; query += "EXISTS (SELECT DISTINCT album_artist.idArtist FROM album_artist, album_genre, genre WHERE album_artist.idArtist = " + GetField(FieldId, strType) + " AND album_artist.idAlbum = album_genre.idAlbum AND album_genre.idGenre = genre.idGenre AND genre.strGenre" + parameter + "))"; } + if (m_field == FieldRole) + { + query = negate + " (EXISTS (SELECT DISTINCT song_artist.idArtist FROM song_artist, role WHERE song_artist.idArtist = " + GetField(FieldId, strType) + " AND song_artist.idRole = role.idRole AND role.strRole" + parameter + "))"; + } } else if (strType == "movies") { diff --git a/xbmc/utils/DatabaseUtils.h b/xbmc/utils/DatabaseUtils.h index 5b3d191210059..cbd5d8fcdc712 100644 --- a/xbmc/utils/DatabaseUtils.h +++ b/xbmc/utils/DatabaseUtils.h @@ -80,6 +80,7 @@ typedef enum { FieldInProgress, FieldRating, FieldComment, + FieldRole, FieldDateAdded, FieldTvShowTitle, FieldPlot,