summaryrefslogtreecommitdiffstats
path: root/xbmc/music/dialogs/GUIDialogMusicInfo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/music/dialogs/GUIDialogMusicInfo.cpp')
-rw-r--r--xbmc/music/dialogs/GUIDialogMusicInfo.cpp1043
1 files changed, 1043 insertions, 0 deletions
diff --git a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp
new file mode 100644
index 0000000..a4b282a
--- /dev/null
+++ b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp
@@ -0,0 +1,1043 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIDialogMusicInfo.h"
+
+#include "FileItem.h"
+#include "GUIPassword.h"
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "TextureCache.h"
+#include "URL.h"
+#include "dialogs/GUIDialogBusy.h"
+#include "dialogs/GUIDialogFileBrowser.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "filesystem/Directory.h"
+#include "filesystem/MusicDatabaseDirectory/QueryParams.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "input/Key.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "music/MusicDatabase.h"
+#include "music/MusicLibraryQueue.h"
+#include "music/MusicThumbLoader.h"
+#include "music/MusicUtils.h"
+#include "music/dialogs/GUIDialogSongInfo.h"
+#include "music/infoscanner/MusicInfoScanner.h"
+#include "music/tags/MusicInfoTag.h"
+#include "music/windows/GUIWindowMusicBase.h"
+#include "profiles/ProfileManager.h"
+#include "settings/MediaSourceSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "storage/MediaManager.h"
+#include "utils/FileExtensionProvider.h"
+#include "utils/FileUtils.h"
+#include "utils/ProgressJob.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+
+using namespace XFILE;
+using namespace MUSIC_INFO;
+using namespace MUSICDATABASEDIRECTORY;
+using namespace KODI::MESSAGING;
+
+#define CONTROL_BTN_REFRESH 6
+#define CONTROL_USERRATING 7
+#define CONTROL_BTN_PLAY 8
+#define CONTROL_BTN_GET_THUMB 10
+#define CONTROL_ARTISTINFO 12
+
+#define CONTROL_LIST 50
+
+#define TIME_TO_BUSY_DIALOG 500
+
+class CGetInfoJob : public CJob
+{
+public:
+ ~CGetInfoJob(void) override = default;
+
+ // Fetch full album/artist information including art types list
+ bool DoWork() override
+ {
+ CGUIDialogMusicInfo *dialog = CServiceBroker::GetGUI()->GetWindowManager().
+ GetWindow<CGUIDialogMusicInfo>(WINDOW_DIALOG_MUSIC_INFO);
+ if (!dialog)
+ return false;
+ if (dialog->IsCancelled())
+ return false;
+ CFileItemPtr m_item = dialog->GetCurrentListItem();
+ CMusicInfoTag& tag = *m_item->GetMusicInfoTag();
+
+ CMusicDatabase database;
+ database.Open();
+ // May only have partially populated music item, so fetch all artist or album data from db
+ if (tag.GetType() == MediaTypeArtist)
+ {
+ int artistId = tag.GetDatabaseId();
+ CArtist artist;
+ if (!database.GetArtist(artistId, artist))
+ return false;
+ tag.SetArtist(artist);
+ CMusicDatabase::SetPropertiesFromArtist(*m_item, artist);
+ m_item->SetLabel(artist.strArtist);
+
+ // Get artist folder where local art could be found
+ // Get the *name* of the folder for this artist within the Artist Info folder (may not exist).
+ // If there is no Artist Info folder specified in settings this will be blank
+ database.GetArtistPath(artist, artist.strPath);
+ // Get the old location for those album artists with a unique folder (local to music files)
+ // If there is no folder for the artist and *only* the artist this will be blank
+ std::string oldartistpath;
+ bool oldpathfound = database.GetOldArtistPath(artist.idArtist, oldartistpath);
+
+ // Set up path for *item folder when browsing for art, by default this is
+ // in the Artist Info Folder (when it exists), but could end up blank
+ std::string artistItemPath = artist.strPath;
+ if (!CDirectory::Exists(artistItemPath))
+ {
+ // Fall back local to music files (historic location for those album artists with a unique folder)
+ // although there may not be such a unique folder for the arist
+ if (oldpathfound)
+ artistItemPath = oldartistpath;
+ else
+ // Fall back further to browse the Artist Info Folder itself
+ artistItemPath = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER);
+ }
+ m_item->SetPath(artistItemPath);
+
+ // Store info as CArtist as well as item properties
+ dialog->SetArtist(artist, oldartistpath);
+
+ // Fetch artist discography as scraped from online sources, but always
+ // include all the albums in the music library
+ dialog->SetDiscography(database);
+ }
+ else
+ {
+ // tag.GetType == MediaTypeAlbum
+ int albumId = tag.GetDatabaseId();
+ CAlbum album;
+ if (!database.GetAlbum(albumId, album))
+ return false;
+ tag.SetAlbum(album);
+ CMusicDatabase::SetPropertiesFromAlbum(*m_item, album);
+
+ // Get album folder where local art could be found
+ database.GetAlbumPath(albumId, album.strPath);
+ // Set up path for *item folder when browsing for art
+ m_item->SetPath(album.strPath);
+ // Store info as CAlbum as well as item properties
+ dialog->SetAlbum(album, album.strPath);
+
+ // Set the list of songs and related art
+ dialog->SetSongs(album.songs);
+ }
+ database.Close();
+
+ /*
+ Load current art (to CGUIListItem.m_art)
+ For albums this includes related artist(s) art and artist fanart set as
+ fallback album fanart.
+ Clear item art first to ensure fresh not cached/partial art
+ */
+ m_item->ClearArt();
+ CMusicThumbLoader loader;
+ loader.LoadItem(m_item.get());
+
+ // Fill vector of possible art types with current art, when it exists,
+ // for display on the art type selection dialog
+ CFileItemList artlist;
+ MUSIC_UTILS::FillArtTypesList(*m_item, artlist);
+ dialog->SetArtTypeList(artlist);
+ if (dialog->IsCancelled())
+ return false;
+
+ // Tell waiting MusicDialog that job is complete
+ dialog->FetchComplete();
+
+ return true;
+ }
+};
+
+class CSetUserratingJob : public CJob
+{
+ int idAlbum;
+ int iUserrating;
+public:
+ CSetUserratingJob(int albumId, int userrating) :
+ idAlbum(albumId),
+ iUserrating(userrating)
+ { }
+
+ ~CSetUserratingJob(void) override = default;
+
+ bool DoWork(void) override
+ {
+ // Asynchronously update userrating in library
+ CMusicDatabase db;
+ if (db.Open())
+ {
+ db.SetAlbumUserrating(idAlbum, iUserrating);
+ db.Close();
+ }
+
+ return true;
+ }
+};
+
+class CRefreshInfoJob : public CProgressJob
+{
+public:
+ CRefreshInfoJob(CGUIDialogProgress* progressDialog)
+ : CProgressJob(nullptr)
+ {
+ if (progressDialog)
+ SetProgressIndicators(nullptr, progressDialog);
+ SetAutoClose(true);
+ }
+
+ ~CRefreshInfoJob(void) override = default;
+
+ // Refresh album/artist information including art types list
+ bool DoWork() override
+ {
+ CGUIDialogMusicInfo *dialog = CServiceBroker::GetGUI()->GetWindowManager().
+ GetWindow<CGUIDialogMusicInfo>(WINDOW_DIALOG_MUSIC_INFO);
+ if (!dialog)
+ return false;
+ if (dialog->IsCancelled())
+ return false;
+ CFileItemPtr m_item = dialog->GetCurrentListItem();
+ CMusicInfoTag& tag = *m_item->GetMusicInfoTag();
+ CArtist& m_artist = dialog->GetArtist();
+ CAlbum& m_album = dialog->GetAlbum();
+
+ CGUIDialogProgress* dlgProgress = GetProgressDialog();
+ CMusicDatabase database;
+ database.Open();
+ if (tag.GetType() == MediaTypeArtist)
+ {
+ ADDON::ScraperPtr scraper;
+ if (!database.GetScraper(m_artist.idArtist, CONTENT_ARTISTS, scraper))
+ return false;
+
+ if (dlgProgress->IsCanceled())
+ return false;
+ database.ClearArtistLastScrapedTime(m_artist.idArtist);
+
+ if (dlgProgress->IsCanceled())
+ return false;
+ CMusicInfoScanner scanner;
+ if (scanner.UpdateArtistInfo(m_artist, scraper, true, dlgProgress) != CInfoScanner::INFO_ADDED)
+ return false;
+ else
+ // Tell info dialog, so can show message
+ dialog->SetScrapedInfo(true);
+
+ if (dlgProgress->IsCanceled())
+ return false;
+ //That changed DB and m_artist, now update dialog item with new info and art
+ tag.SetArtist(m_artist);
+ CMusicDatabase::SetPropertiesFromArtist(*m_item, m_artist);
+
+ // Fetch artist discography as scraped from online sources, but always
+ // include all the albums in the music library
+ dialog->SetDiscography(database);
+ }
+ else
+ {
+ // tag.GetType == MediaTypeAlbum
+ ADDON::ScraperPtr scraper;
+ if (!database.GetScraper(m_album.idAlbum, CONTENT_ALBUMS, scraper))
+ return false;
+
+ if (dlgProgress->IsCanceled())
+ return false;
+ database.ClearAlbumLastScrapedTime(m_album.idAlbum);
+
+ if (dlgProgress->IsCanceled())
+ return false;
+ CMusicInfoScanner scanner;
+ if (scanner.UpdateAlbumInfo(m_album, scraper, true, GetProgressDialog()) != CInfoScanner::INFO_ADDED)
+ return false;
+ else
+ // Tell info dialog, so can show message
+ dialog->SetScrapedInfo(true);
+
+ if (dlgProgress->IsCanceled())
+ return false;
+ //That changed DB and m_album, now update dialog item with new info and art
+ // Album songs are unchanged by refresh (even with Musicbrainz sync?)
+ tag.SetAlbum(m_album);
+ CMusicDatabase::SetPropertiesFromAlbum(*m_item, m_album);
+
+ // Set the list of songs and related art
+ dialog->SetSongs(m_album.songs);
+ }
+ database.Close();
+
+ if (dlgProgress->IsCanceled())
+ return false;
+ /*
+ Load current art (to CGUIListItem.m_art)
+ For albums this includes related artist(s) art and artist fanart set as
+ fallback album fanart.
+ Clear item art first to ensure fresh not cached/partial art
+ */
+ m_item->ClearArt();
+ CMusicThumbLoader loader;
+ loader.LoadItem(m_item.get());
+
+ if (dlgProgress->IsCanceled())
+ return false;
+ // Fill vector of possible art types with current art, when it exists,
+ // for display on the art type selection dialog
+ CFileItemList artlist;
+ MUSIC_UTILS::FillArtTypesList(*m_item, artlist);
+ dialog->SetArtTypeList(artlist);
+ if (dialog->IsCancelled())
+ return false;
+
+ // Tell waiting MusicDialog that job is complete
+ MarkFinished();
+ return true;
+ }
+};
+
+CGUIDialogMusicInfo::CGUIDialogMusicInfo(void)
+ : CGUIDialog(WINDOW_DIALOG_MUSIC_INFO, "DialogMusicInfo.xml"),
+ m_albumSongs(new CFileItemList),
+ m_item(new CFileItem),
+ m_artTypeList(new CFileItemList)
+{
+ m_loadType = KEEP_IN_MEMORY;
+}
+
+CGUIDialogMusicInfo::~CGUIDialogMusicInfo(void)
+{
+}
+
+bool CGUIDialogMusicInfo::OnMessage(CGUIMessage& message)
+{
+ switch ( message.GetMessage() )
+ {
+ case GUI_MSG_WINDOW_DEINIT:
+ {
+ m_artTypeList->Clear();
+ // For albums update user rating if it has changed
+ if (!m_bArtistInfo && m_startUserrating != m_item->GetMusicInfoTag()->GetUserrating())
+ {
+ m_hasUpdatedUserrating = true;
+
+ // Asynchronously update song userrating in library
+ CSetUserratingJob *job = new CSetUserratingJob(m_item->GetMusicInfoTag()->GetAlbumId(),
+ m_item->GetMusicInfoTag()->GetUserrating());
+ CServiceBroker::GetJobManager()->AddJob(job, nullptr);
+ }
+ if (m_hasRefreshed || m_hasUpdatedUserrating)
+ {
+ // Send a message to all windows to tell them to update the item.
+ // This communicates changes to the music lib window.
+ // The music lib window item is updated to but changes to the rating when it is the sort
+ // do not show on screen until refresh() that fetches the list from scratch, sorts etc.
+ CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_item);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ }
+
+ CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_LIST);
+ OnMessage(msg);
+ m_albumSongs->Clear();
+ }
+ break;
+
+ case GUI_MSG_WINDOW_INIT:
+ {
+ CGUIDialog::OnMessage(message);
+ Update();
+ m_cancelled = false;
+ return true;
+ }
+ break;
+
+
+ case GUI_MSG_CLICKED:
+ {
+ int iControl = message.GetSenderId();
+ if (iControl == CONTROL_USERRATING)
+ {
+ OnSetUserrating();
+ }
+ else if (iControl == CONTROL_BTN_REFRESH)
+ {
+ RefreshInfo();
+ return true;
+ }
+ else if (iControl == CONTROL_BTN_GET_THUMB)
+ {
+ OnGetArt();
+ return true;
+ }
+ else if (iControl == CONTROL_ARTISTINFO)
+ {
+ if (!m_bArtistInfo)
+ OnArtistInfo(m_album.artistCredits[0].GetArtistId());
+ return true;
+ }
+ else if (iControl == CONTROL_LIST)
+ {
+ int iAction = message.GetParam1();
+ if (m_bArtistInfo && (ACTION_SELECT_ITEM == iAction || ACTION_MOUSE_LEFT_CLICK == iAction))
+ {
+ CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), iControl);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ int iItem = msg.GetParam1();
+ int id = -1;
+ if (iItem >= 0 && iItem < m_albumSongs->Size())
+ id = m_albumSongs->Get(iItem)->GetMusicInfoTag()->GetDatabaseId();
+ if (id > 0)
+ {
+ OnAlbumInfo(id);
+ return true;
+ }
+ }
+ }
+ else if (iControl == CONTROL_BTN_PLAY)
+ {
+ if (m_album.idAlbum >= 0)
+ {
+ // Play album
+ const std::string path = StringUtils::Format("musicdb://albums/{}", m_album.idAlbum);
+ OnPlayItem(std::make_shared<CFileItem>(path, m_album));
+ return true;
+ }
+ else
+ {
+ CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), iControl);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ const int iItem = msg.GetParam1();
+ if (iItem >= 0 && iItem < m_albumSongs->Size())
+ {
+ // Play selected song
+ OnPlayItem(m_albumSongs->Get(iItem));
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ break;
+ }
+
+ return CGUIDialog::OnMessage(message);
+}
+
+bool CGUIDialogMusicInfo::OnAction(const CAction &action)
+{
+ int userrating = m_item->GetMusicInfoTag()->GetUserrating();
+ if (action.GetID() == ACTION_INCREASE_RATING)
+ {
+ SetUserrating(userrating + 1);
+ return true;
+ }
+ else if (action.GetID() == ACTION_DECREASE_RATING)
+ {
+ SetUserrating(userrating - 1);
+ return true;
+ }
+ else if (action.GetID() == ACTION_SHOW_INFO)
+ {
+ Close();
+ return true;
+ }
+ return CGUIDialog::OnAction(action);
+}
+
+bool CGUIDialogMusicInfo::SetItem(CFileItem* item)
+{
+ *m_item = *item;
+ m_event.Reset();
+ m_cancelled = false; // Happens before win_init
+
+ // In a separate job fetch info and fill list of art types.
+ int jobid =
+ CServiceBroker::GetJobManager()->AddJob(new CGetInfoJob(), nullptr, CJob::PRIORITY_LOW);
+
+ // Wait to get all data before show, allowing user to cancel if fetch is slow
+ if (!CGUIDialogBusy::WaitOnEvent(m_event, TIME_TO_BUSY_DIALOG))
+ {
+ // Cancel job still waiting in queue (unlikely)
+ CServiceBroker::GetJobManager()->CancelJob(jobid);
+ // Flag to stop job already in progress
+ m_cancelled = true;
+ return false;
+ }
+
+ return true;
+}
+
+void CGUIDialogMusicInfo::SetAlbum(const CAlbum& album, const std::string &path)
+{
+ m_album = album;
+ m_item->SetPath(album.strPath);
+
+ m_startUserrating = m_album.iUserrating;
+ m_fallbackartpath.clear();
+ m_bArtistInfo = false;
+ m_hasUpdatedUserrating = false;
+ m_hasRefreshed = false;
+}
+
+void CGUIDialogMusicInfo::SetArtist(const CArtist& artist, const std::string &path)
+{
+ m_artist = artist;
+ m_fallbackartpath = path;
+ m_bArtistInfo = true;
+ m_hasRefreshed = false;
+}
+
+void CGUIDialogMusicInfo::SetSongs(const VECSONGS &songs) const
+{
+ m_albumSongs->Clear();
+ CMusicThumbLoader loader;
+ for (unsigned int i = 0; i < songs.size(); i++)
+ {
+ const CSong& song = songs[i];
+ CFileItemPtr item(new CFileItem(song));
+ // Load the song art and related artist(s) (that may be different from album artist) art
+ loader.LoadItem(item.get());
+ m_albumSongs->Add(item);
+ }
+}
+
+void CGUIDialogMusicInfo::SetDiscography(CMusicDatabase& database) const
+{
+ m_albumSongs->Clear();
+ database.GetArtistDiscography(m_artist.idArtist, *m_albumSongs);
+ CMusicThumbLoader loader;
+ for (const auto& item : *m_albumSongs)
+ {
+ // Load all the album art and related artist(s) art (could be other collaborating artists)
+ loader.LoadItem(item.get());
+ if (item->GetMusicInfoTag()->GetDatabaseId() == -1)
+ item->SetArt("thumb", "DefaultAlbumCover.png");
+ }
+}
+
+void CGUIDialogMusicInfo::Update()
+{
+ if (m_bArtistInfo)
+ {
+ SET_CONTROL_HIDDEN(CONTROL_ARTISTINFO);
+ SET_CONTROL_HIDDEN(CONTROL_USERRATING);
+
+ CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), CONTROL_LIST, 0, 0, m_albumSongs.get());
+ OnMessage(message);
+
+ }
+ else
+ {
+ SET_CONTROL_VISIBLE(CONTROL_ARTISTINFO);
+ SET_CONTROL_VISIBLE(CONTROL_USERRATING);
+
+ CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), CONTROL_LIST, 0, 0, m_albumSongs.get());
+ OnMessage(message);
+
+ }
+
+ const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
+
+ // Disable the Choose Art button if the user isn't allowed it
+ CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_GET_THUMB,
+ profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser);
+}
+
+void CGUIDialogMusicInfo::SetLabel(int iControl, const std::string& strLabel)
+{
+ if (strLabel.empty())
+ {
+ SET_CONTROL_LABEL(iControl, 416);
+ }
+ else
+ {
+ SET_CONTROL_LABEL(iControl, strLabel);
+ }
+}
+
+void CGUIDialogMusicInfo::OnInitWindow()
+{
+ SET_CONTROL_LABEL(CONTROL_BTN_REFRESH, 184);
+ SET_CONTROL_LABEL(CONTROL_USERRATING, 38023);
+ SET_CONTROL_LABEL(CONTROL_BTN_GET_THUMB, 13511);
+ SET_CONTROL_LABEL(CONTROL_ARTISTINFO, 21891);
+ SET_CONTROL_LABEL(CONTROL_BTN_PLAY, 208);
+
+ if (m_bArtistInfo)
+ {
+ SET_CONTROL_HIDDEN(CONTROL_ARTISTINFO);
+ SET_CONTROL_HIDDEN(CONTROL_USERRATING);
+ SET_CONTROL_HIDDEN(CONTROL_BTN_PLAY);
+ }
+ CGUIDialog::OnInitWindow();
+}
+
+void CGUIDialogMusicInfo::FetchComplete()
+{
+ //Trigger the event to indicate data has been fetched
+ m_event.Set();
+}
+
+
+void CGUIDialogMusicInfo::RefreshInfo()
+{
+ // Double check we have permission (button should be hidden when not)
+ const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
+ if (!profileManager->GetCurrentProfile().canWriteDatabases() && !g_passwordManager.bMasterUser)
+ return;
+
+ // Check if scanning
+ if (CMusicLibraryQueue::GetInstance().IsScanningLibrary())
+ {
+ HELPERS::ShowOKDialogText(CVariant{ 189 }, CVariant{ 14057 });
+ return;
+ }
+
+ CGUIDialogProgress* dlgProgress = CServiceBroker::GetGUI()->GetWindowManager().
+ GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
+ if (!dlgProgress)
+ return;
+
+ if (m_bArtistInfo)
+ { // Show dialog box indicating we're searching for the artist
+ dlgProgress->SetHeading(CVariant{ 21889 });
+ dlgProgress->SetLine(0, CVariant{ m_artist.strArtist });
+ dlgProgress->SetLine(1, CVariant{ "" });
+ dlgProgress->SetLine(2, CVariant{ "" });
+ }
+ else
+ { // Show dialog box indicating we're searching for the album
+ dlgProgress->SetHeading(CVariant{ 185 });
+ dlgProgress->SetLine(0, CVariant{ m_album.strAlbum });
+ dlgProgress->SetLine(1, CVariant{ m_album.strArtistDesc });
+ dlgProgress->SetLine(2, CVariant{ "" });
+ }
+ dlgProgress->Open();
+
+ SetScrapedInfo(false);
+ // Start separate job to scrape info and fill list of art types.
+ CServiceBroker::GetJobManager()->AddJob(new CRefreshInfoJob(dlgProgress), nullptr,
+ CJob::PRIORITY_HIGH);
+
+ // Wait for refresh to complete or be canceled, but render every 10ms so that the
+ // pointer movements works on dialog even when job is reporting progress infrequently
+ if (dlgProgress)
+ dlgProgress->Wait(10);
+
+ if (dlgProgress->IsCanceled())
+ {
+ return;
+ }
+
+ // Show message when scraper was unsuccessful
+ if (!HasScrapedInfo())
+ {
+ if (m_bArtistInfo)
+ HELPERS::ShowOKDialogText(CVariant{ 21889 }, CVariant{ 20199 });
+ else
+ HELPERS::ShowOKDialogText(CVariant{ 185 }, CVariant{ 500 });
+ return;
+ }
+
+ // Show new values on screen
+ Update();
+ m_hasRefreshed = true;
+
+ if (dlgProgress)
+ dlgProgress->Close();
+}
+
+void CGUIDialogMusicInfo::SetUserrating(int userrating) const
+{
+ userrating = std::max(userrating, 0);
+ userrating = std::min(userrating, 10);
+ if (userrating != m_item->GetMusicInfoTag()->GetUserrating())
+ {
+ m_item->GetMusicInfoTag()->SetUserrating(userrating);
+ }
+}
+
+void CGUIDialogMusicInfo::OnAlbumInfo(int id)
+{
+ // Switch to show album info for given album ID
+ // Close current (artist) dialog to save art changes
+ Close(true);
+ ShowForAlbum(id);
+}
+
+void CGUIDialogMusicInfo::OnArtistInfo(int id)
+{
+ // Switch to show artist info for given artist ID
+ // Close current (album) dialog to save art and rating changes
+ Close(true);
+ ShowForArtist(id);
+}
+
+CFileItemPtr CGUIDialogMusicInfo::GetCurrentListItem(int offset)
+{
+ return m_item;
+}
+
+std::string CGUIDialogMusicInfo::GetContent()
+{
+ if (m_item->GetMusicInfoTag()->GetType() == MediaTypeArtist)
+ return "artists";
+ else
+ return "albums";
+}
+
+void CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(VECSOURCES &sources, const CFileItem &item)
+{
+ std::string itemDir;
+ std::string artistFolder;
+
+ itemDir = item.GetPath();
+ if (item.HasMusicInfoTag())
+ {
+ if (item.GetMusicInfoTag()->GetType() == MediaTypeSong)
+ itemDir = URIUtils::GetParentPath(item.GetMusicInfoTag()->GetURL());
+
+ // For artist add Artist Info Folder path to browser sources
+ if (item.GetMusicInfoTag()->GetType() == MediaTypeArtist)
+ {
+ artistFolder = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_MUSICLIBRARY_ARTISTSFOLDER);
+ if (!artistFolder.empty() && artistFolder.compare(itemDir) == 0)
+ itemDir.clear(); // skip *item when artist not have a unique path
+ }
+ }
+ // Add "*Item folder" path to file browser sources
+ if (!itemDir.empty() && CDirectory::Exists(itemDir))
+ {
+ CMediaSource itemSource;
+ itemSource.strName = g_localizeStrings.Get(36041);
+ itemSource.strPath = itemDir;
+ sources.push_back(itemSource);
+ }
+
+ // For artist add Artist Info Folder path to browser sources
+ if (!artistFolder.empty() && CDirectory::Exists(artistFolder))
+ {
+ CMediaSource itemSource;
+ itemSource.strName = "* " + g_localizeStrings.Get(20223);
+ itemSource.strPath = artistFolder;
+ sources.push_back(itemSource);
+ }
+}
+
+void CGUIDialogMusicInfo::SetArtTypeList(CFileItemList& artlist)
+{
+ m_artTypeList->Clear();
+ m_artTypeList->Copy(artlist);
+}
+
+/*
+Allow user to choose artwork for the artist or album
+For each type of art the options are:
+1. Current art
+2. Local art (thumb found by filename)
+3. Remote art (scraped list of urls from online sources e.g. fanart.tv)
+5. Embedded art (@todo)
+6. None
+*/
+void CGUIDialogMusicInfo::OnGetArt()
+{
+ std::string type = MUSIC_UTILS::ShowSelectArtTypeDialog(*m_artTypeList);
+ if (type.empty())
+ return; // Cancelled
+
+ CFileItemList items;
+ CGUIListItem::ArtMap primeArt = m_item->GetArt(); // art without fallbacks
+ bool bHasArt = m_item->HasArt(type);
+ bool bFallback(false);
+ if (bHasArt)
+ {
+ // Check if that type of art is actually a fallback, e.g. artist fanart
+ CGUIListItem::ArtMap::const_iterator i = primeArt.find(type);
+ bFallback = (i == primeArt.end());
+ }
+
+ // Build list of possible images of that art type
+ if (bHasArt)
+ {
+ // Add item for current artwork
+ // For album it could be a fallback from artist
+ CFileItemPtr item(new CFileItem("thumb://Current", false));
+ item->SetArt("thumb", m_item->GetArt(type));
+ item->SetArt("icon", "DefaultPicture.png");
+ item->SetLabel(g_localizeStrings.Get(13512));
+ items.Add(item);
+ }
+
+ // Grab the thumbnails of this art type scraped from the web
+ std::vector<std::string> remotethumbs;
+ // Art type is encoded into the scraper XML as optional "aspect=" field
+ // Type "thumb" returns URLs for all types of art including those without aspect.
+ // Those URL without aspect are also returned for all other type values.
+ if (m_bArtistInfo)
+ m_artist.thumbURL.GetThumbUrls(remotethumbs, type);
+ else
+ m_album.thumbURL.GetThumbUrls(remotethumbs, type);
+
+ for (unsigned int i = 0; i < remotethumbs.size(); ++i)
+ {
+ std::string strItemPath;
+ strItemPath = StringUtils::Format("thumb://Remote{}", i);
+ CFileItemPtr item(new CFileItem(strItemPath, false));
+ item->SetArt("thumb", remotethumbs[i]);
+ item->SetArt("icon", "DefaultPicture.png");
+ item->SetLabel(g_localizeStrings.Get(13513));
+
+ items.Add(item);
+ }
+
+ // Local art
+ std::string localArt;
+ std::vector<std::string> paths;
+ if (m_bArtistInfo)
+ {
+ // Individual artist subfolder within the Artist Information Folder
+ paths.emplace_back(m_artist.strPath);
+ // Fallback local to music files (when there is a unique folder)
+ paths.emplace_back(m_fallbackartpath);
+ }
+ else
+ // Album folder, when a unique one exists, no fallback
+ paths.emplace_back(m_album.strPath);
+ for (const auto& path : paths)
+ {
+ if (!localArt.empty() && CFileUtils::Exists(localArt))
+ break;
+ if (!path.empty())
+ {
+ CFileItem item(path, true);
+ if (type == "thumb")
+ // Local music thumbnail images named by <musicthumbs>
+ localArt = item.GetUserMusicThumb(true);
+ else
+ { // Check case and ext insenitively for local images with type as name
+ // e.g. <arttype>.jpg
+ CFileItemList items;
+ CDirectory::GetDirectory(path, items,
+ CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(),
+ DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO);
+
+ for (int j = 0; j < items.Size(); j++)
+ {
+ std::string strCandidate = URIUtils::GetFileName(items[j]->GetPath());
+ URIUtils::RemoveExtension(strCandidate);
+ if (StringUtils::EqualsNoCase(strCandidate, type))
+ {
+ localArt = items[j]->GetPath();
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!localArt.empty() && CFileUtils::Exists(localArt))
+ {
+ CFileItemPtr item(new CFileItem("Local Art: " + localArt, false));
+ item->SetArt("thumb", localArt);
+ item->SetLabel(g_localizeStrings.Get(13514)); // "Local art"
+ items.Add(item);
+ }
+
+ // No art
+ if (bHasArt && !bFallback)
+ { // Actually has this type of art (not a fallback) so
+ // allow the user to delete it by selecting "no art".
+ CFileItemPtr item(new CFileItem("thumb://None", false));
+ if (m_bArtistInfo)
+ item->SetArt("icon", "DefaultArtist.png");
+ else
+ item->SetArt("icon", "DefaultAlbumCover.png");
+ item->SetLabel(g_localizeStrings.Get(13515));
+ items.Add(item);
+ }
+
+ //! @todo: Add support for extracting embedded art from song files to use for album
+
+ // Clear local images of this type from cache so user will see any recent
+ // local file changes immediately
+ for (auto& item : items)
+ {
+ // Skip images from remote sources, recache done by refresh (could be slow)
+ if (StringUtils::StartsWith(item->GetPath(), "thumb://Remote"))
+ continue;
+ std::string thumb(item->GetArt("thumb"));
+ if (thumb.empty())
+ continue;
+ CURL url(CTextureUtils::UnwrapImageURL(thumb));
+ // Skip images from remote sources (current thumb could be remote)
+ if (url.IsProtocol("http") || url.IsProtocol("https"))
+ continue;
+ CServiceBroker::GetTextureCache()->ClearCachedImage(thumb);
+ // Remove any thumbnail of local image too (created when browsing files)
+ std::string thumbthumb(CTextureUtils::GetWrappedThumbURL(thumb));
+ CServiceBroker::GetTextureCache()->ClearCachedImage(thumbthumb);
+ }
+
+ // Show list of possible art for user selection
+ // Note that during selection thumbs of *all* images shown are cached. When
+ // browsing folders there could be many irrelevant thumbs cached that are
+ // never used by Kodi again, but there is no obvious way to clear these
+ // thumbs from the cache automatically.
+ std::string result;
+ VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("music"));
+ CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(sources, *m_item);
+ CServiceBroker::GetMediaManager().GetLocalDrives(sources);
+ if (CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(13511), result) &&
+ result != "thumb://Current")
+ {
+ // User didn't choose the one they have.
+ // Overwrite with the new art or clear it
+ std::string newArt;
+ if (StringUtils::StartsWith(result, "thumb://Remote"))
+ {
+ int number = atoi(result.substr(14).c_str());
+ newArt = remotethumbs[number];
+ }
+ else if (result == "thumb://Thumb")
+ newArt = m_item->GetArt("thumb");
+ else if (StringUtils::StartsWith(result, "Local Art: "))
+ newArt = localArt;
+ else if (CFileUtils::Exists(result))
+ newArt = result;
+ else // none
+ newArt.clear();
+
+ // Asynchronously update that type of art in the database and then
+ // refresh artist, album and fallback art of currently playing song
+ MUSIC_UTILS::UpdateArtJob(m_item, type, newArt);
+
+ // Update local item and art list with current art
+ m_item->SetArt(type, newArt);
+ for (const auto& artitem : *m_artTypeList)
+ {
+ if (artitem->GetProperty("artType") == type)
+ {
+ artitem->SetArt("thumb", newArt);
+ break;
+ }
+ }
+
+ // Get new artwork to show in other places e.g. on music lib window, but this does
+ // not update artist, album or fallback art for the currently playing song as it
+ // is a different item with different ID and media type
+ CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_item);
+ CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg);
+ }
+
+ // Re-open the art type selection dialog as we come back from
+ // the image selection dialog
+ OnGetArt();
+}
+
+void CGUIDialogMusicInfo::OnSetUserrating() const
+{
+ int userrating = MUSIC_UTILS::ShowSelectRatingDialog(m_item->GetMusicInfoTag()->GetUserrating());
+ if (userrating < 0) // Nothing selected, so rating unchanged
+ return;
+
+ SetUserrating(userrating);
+}
+
+
+void CGUIDialogMusicInfo::ShowForAlbum(int idAlbum)
+{
+ std::string path = StringUtils::Format("musicdb://albums/{}", idAlbum);
+ CFileItem item(path, true); // An album, but IsAlbum() not set as didn't use SetAlbum()
+ ShowFor(&item);
+}
+
+void CGUIDialogMusicInfo::ShowForArtist(int idArtist)
+{
+ std::string path = StringUtils::Format("musicdb://artists/{}", idArtist);
+ CFileItem item(path, true);
+ ShowFor(&item);
+}
+
+void CGUIDialogMusicInfo::ShowFor(CFileItem* pItem)
+{
+ if (pItem->IsParentFolder() || URIUtils::IsSpecial(pItem->GetPath()) ||
+ StringUtils::StartsWithNoCase(pItem->GetPath(), "musicsearch://"))
+ return; // nothing to do
+
+ if (!pItem->m_bIsFolder)
+ { // Show Song information dialog
+ CGUIDialogSongInfo::ShowFor(pItem);
+ return;
+ }
+
+ CFileItem musicitem("musicdb://", true);
+
+ // We have a folder album/artist info dialog only shown for db items
+ // or for music video with artist/album in music library
+ if (pItem->IsMusicDb())
+ {
+ if (!pItem->HasMusicInfoTag() || pItem->GetMusicInfoTag()->GetDatabaseId() < 1)
+ {
+ // Maybe only path is set, then set MusicInfoTag
+ CQueryParams params;
+ CDirectoryNode::GetDatabaseInfo(pItem->GetPath(), params);
+ if (params.GetArtistId() > 0)
+ pItem->GetMusicInfoTag()->SetDatabaseId(params.GetArtistId(), MediaTypeArtist);
+ else if (params.GetAlbumId() > 0)
+ pItem->GetMusicInfoTag()->SetDatabaseId(params.GetAlbumId(), MediaTypeAlbum);
+ else
+ return; // nothing to do
+ }
+ musicitem.SetFromMusicInfoTag(*pItem->GetMusicInfoTag());
+ }
+ else if (pItem->HasProperty("artist_musicid"))
+ {
+ musicitem.GetMusicInfoTag()->SetDatabaseId(pItem->GetProperty("artist_musicid").asInteger32(),
+ MediaTypeArtist);
+ }
+ else if (pItem->HasProperty("album_musicid"))
+ {
+ musicitem.GetMusicInfoTag()->SetDatabaseId(pItem->GetProperty("album_musicid").asInteger32(),
+ MediaTypeAlbum);
+ }
+ else
+ return; // nothing to do
+
+
+ CGUIDialogMusicInfo *pDlgMusicInfo = CServiceBroker::GetGUI()->GetWindowManager().
+ GetWindow<CGUIDialogMusicInfo>(WINDOW_DIALOG_MUSIC_INFO);
+ if (pDlgMusicInfo)
+ {
+ if (pDlgMusicInfo->SetItem(&musicitem))
+ {
+ pDlgMusicInfo->Open();
+ if (pItem->GetMusicInfoTag()->GetType() == MediaTypeAlbum &&
+ pDlgMusicInfo->HasUpdatedUserrating())
+ {
+ auto window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowMusicBase>(WINDOW_MUSIC_NAV);
+ if (window)
+ window->RefreshContent("albums");
+ }
+ }
+ }
+}
+
+void CGUIDialogMusicInfo::OnPlayItem(const std::shared_ptr<CFileItem>& item)
+{
+ Close(true);
+ MUSIC_UTILS::PlayItem(item);
+}