/* * 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 "GUIDialogSongInfo.h" #include "GUIDialogMusicInfo.h" #include "GUIPassword.h" #include "GUIUserMessages.h" #include "ServiceBroker.h" #include "TextureCache.h" #include "Util.h" #include "dialogs/GUIDialogBusy.h" #include "dialogs/GUIDialogFileBrowser.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" #include "input/Key.h" #include "music/MusicDatabase.h" #include "music/MusicUtils.h" #include "music/tags/MusicInfoTag.h" #include "music/windows/GUIWindowMusicBase.h" #include "profiles/ProfileManager.h" #include "settings/MediaSourceSettings.h" #include "settings/SettingsComponent.h" #include "storage/MediaManager.h" #include "utils/FileUtils.h" #define CONTROL_BTN_REFRESH 6 #define CONTROL_USERRATING 7 #define CONTROL_BTN_PLAY 8 #define CONTROL_BTN_GET_THUMB 10 #define CONTROL_ALBUMINFO 12 #define CONTROL_LIST 50 #define TIME_TO_BUSY_DIALOG 500 class CGetSongInfoJob : public CJob { public: ~CGetSongInfoJob(void) override = default; // Fetch full song information including art types list bool DoWork() override { CGUIDialogSongInfo *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SONG_INFO); if (!dialog) return false; if (dialog->IsCancelled()) return false; CFileItemPtr m_song = dialog->GetCurrentListItem(); // Fetch tag data from library using filename of item path, or scanning file // (if item does not already have this loaded) if (!m_song->LoadMusicTag()) { // Stop SongInfoDialog waiting dialog->FetchComplete(); return false; } if (dialog->IsCancelled()) return false; // Fetch album and primary song artist data from library as properties // and lyrics by scanning tags from file MUSIC_INFO::CMusicInfoLoader::LoadAdditionalTagInfo(m_song.get()); if (dialog->IsCancelled()) return false; // Get album path (for use in browsing art selection) std::string albumpath; CMusicDatabase db; db.Open(); db.GetAlbumPath(m_song->GetMusicInfoTag()->GetAlbumId(), albumpath); m_song->SetProperty("album_path", albumpath); db.Close(); if (dialog->IsCancelled()) return false; // Load song art. // For songs in library this includes related album and artist(s) art. // Also fetches artist art for non library songs when artist can be found // uniquely by name, otherwise just embedded or cached thumb is fetched. CMusicThumbLoader loader; loader.LoadItem(m_song.get()); if (dialog->IsCancelled()) return false; // For songs in library 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_song, artlist); dialog->SetArtTypeList(artlist); if (dialog->IsCancelled()) return false; // Tell waiting SongInfoDialog that job is complete dialog->FetchComplete(); return true; } }; CGUIDialogSongInfo::CGUIDialogSongInfo(void) : CGUIDialog(WINDOW_DIALOG_SONG_INFO, "DialogMusicInfo.xml") , m_song(new CFileItem) { m_cancelled = false; m_hasUpdatedUserrating = false; m_startUserrating = -1; m_artTypeList.Clear(); m_loadType = KEEP_IN_MEMORY; } CGUIDialogSongInfo::~CGUIDialogSongInfo(void) = default; bool CGUIDialogSongInfo::OnMessage(CGUIMessage& message) { switch (message.GetMessage()) { case GUI_MSG_WINDOW_DEINIT: { m_artTypeList.Clear(); if (m_startUserrating != m_song->GetMusicInfoTag()->GetUserrating()) { m_hasUpdatedUserrating = true; // Asynchronously update song userrating in library MUSIC_UTILS::UpdateSongRatingJob(m_song, m_song->GetMusicInfoTag()->GetUserrating()); // Send a message to all windows to tell them to update the fileitem // This communicates the rating change to the music lib window, current playlist and OSD. // 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_song); CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); } CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_LIST); OnMessage(msg); break; } case GUI_MSG_WINDOW_INIT: CGUIDialog::OnMessage(message); Update(); m_cancelled = false; break; case GUI_MSG_CLICKED: { int iControl = message.GetSenderId(); if (iControl == CONTROL_USERRATING) { OnSetUserrating(); } else if (iControl == CONTROL_ALBUMINFO) { CGUIDialogMusicInfo::ShowForAlbum(m_albumId); return true; } else if (iControl == CONTROL_BTN_GET_THUMB) { OnGetArt(); return true; } else if (iControl == CONTROL_LIST) { int iAction = message.GetParam1(); if ((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(); if (iItem < 0 || iItem >= static_cast(m_song->GetMusicInfoTag()->GetContributors().size())) break; int idArtist = m_song->GetMusicInfoTag()->GetContributors()[iItem].GetArtistId(); if (idArtist > 0) CGUIDialogMusicInfo::ShowForArtist(idArtist); return true; } } else if (iControl == CONTROL_BTN_PLAY) { OnPlaySong(m_song); return true; } return false; } break; } return CGUIDialog::OnMessage(message); } bool CGUIDialogSongInfo::OnAction(const CAction& action) { int userrating = m_song->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 CGUIDialogSongInfo::OnBack(int actionID) { m_cancelled = true; return CGUIDialog::OnBack(actionID); } void CGUIDialogSongInfo::FetchComplete() { //Trigger the event to indicate data has been fetched m_event.Set(); } void CGUIDialogSongInfo::OnInitWindow() { // Enable album info button when we know album m_albumId = m_song->GetMusicInfoTag()->GetAlbumId(); CONTROL_ENABLE_ON_CONDITION(CONTROL_ALBUMINFO, m_albumId > 0); // Disable music user rating button for plugins as they don't have tables to save this if (m_song->IsPlugin()) CONTROL_DISABLE(CONTROL_USERRATING); else CONTROL_ENABLE(CONTROL_USERRATING); // Disable the Choose Art button if the user isn't allowed it const std::shared_ptr profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_GET_THUMB, profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser); SET_CONTROL_HIDDEN(CONTROL_BTN_REFRESH); SET_CONTROL_LABEL(CONTROL_USERRATING, 38023); SET_CONTROL_LABEL(CONTROL_BTN_GET_THUMB, 13511); SET_CONTROL_LABEL(CONTROL_ALBUMINFO, 10523); SET_CONTROL_LABEL(CONTROL_BTN_PLAY, 208); CGUIDialog::OnInitWindow(); } void CGUIDialogSongInfo::Update() { CFileItemList items; for (const auto& contributor : m_song->GetMusicInfoTag()->GetContributors()) { auto item = std::make_shared(contributor.GetRoleDesc()); item->SetLabel2(contributor.GetArtist()); item->GetMusicInfoTag()->SetDatabaseId(contributor.GetArtistId(), MediaTypeArtist); items.Add(std::move(item)); } CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), CONTROL_LIST, 0, 0, &items); OnMessage(message); } void CGUIDialogSongInfo::SetUserrating(int userrating) { userrating = std::max(userrating, 0); userrating = std::min(userrating, 10); if (userrating != m_song->GetMusicInfoTag()->GetUserrating()) { m_song->GetMusicInfoTag()->SetUserrating(userrating); } } bool CGUIDialogSongInfo::SetSong(CFileItem* item) { *m_song = *item; m_event.Reset(); m_cancelled = false; // SetSong happens before win_init // In a separate job fetch song info and fill list of art types. int jobid = CServiceBroker::GetJobManager()->AddJob(new CGetSongInfoJob(), 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; } // Store initial userrating m_startUserrating = m_song->GetMusicInfoTag()->GetUserrating(); m_hasUpdatedUserrating = false; return true; } void CGUIDialogSongInfo::SetArtTypeList(CFileItemList& artlist) { m_artTypeList.Copy(artlist); } CFileItemPtr CGUIDialogSongInfo::GetCurrentListItem(int offset) { return m_song; } std::string CGUIDialogSongInfo::GetContent() { return "songs"; } /* Allow user to choose artwork for the song For each type of art the options are: 1. Current art 2. Local art (thumb found by filename) 3. Embedded art (@todo) 4. None Note that songs are not scraped, hence there is no list of urls for possible remote art */ void CGUIDialogSongInfo::OnGetArt() { std::string type = MUSIC_UTILS::ShowSelectArtTypeDialog(m_artTypeList); if (type.empty()) return; // Cancelled CFileItemList items; CGUIListItem::ArtMap primeArt = m_song->GetArt(); // Song art without fallbacks bool bHasArt = m_song->HasArt(type); bool bFallback(false); if (bHasArt) { // Check if that type of art is actually a fallback, e.g. album thumb or 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, could a fallback from album/artist CFileItemPtr item(new CFileItem("thumb://Current", false)); item->SetArt("thumb", m_song->GetArt(type)); item->SetArt("icon", "DefaultPicture.png"); item->SetLabel(g_localizeStrings.Get(13512)); //! @todo: label fallback art so user knows? items.Add(item); } else if (m_song->HasArt("thumb")) { // For missing art of that type add the thumb (when it exists and not a fallback) CGUIListItem::ArtMap::const_iterator i = primeArt.find("thumb"); if (i != primeArt.end()) { CFileItemPtr item(new CFileItem("thumb://Thumb", false)); item->SetArt("thumb", m_song->GetArt("thumb")); item->SetArt("icon", "DefaultAlbumCover.png"); item->SetLabel(g_localizeStrings.Get(21371)); items.Add(item); } } std::string localThumb; if (type == "thumb") { // Local thumb type art held in .tbn (for non-library items) localThumb = m_song->GetUserMusicThumb(true); if (m_song->IsMusicDb()) { CFileItem item(m_song->GetMusicInfoTag()->GetURL(), false); localThumb = item.GetUserMusicThumb(true); } if (CFileUtils::Exists(localThumb)) { CFileItemPtr item(new CFileItem("thumb://Local", false)); item->SetArt("thumb", localThumb); item->SetLabel(g_localizeStrings.Get(20017)); items.Add(item); } } // Clear these local images from cache so user will see any recent // local file changes immediately for (auto& item : items) { std::string thumb(item->GetArt("thumb")); if (thumb.empty()) 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); } 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)); item->SetArt("thumb", "DefaultAlbumCover.png"); item->SetLabel(g_localizeStrings.Get(13515)); items.Add(item); } //! @todo: Add support for extracting embedded art // Show list of possible art for user selection std::string result; VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("music")); // Add album folder as source (could be disc set) std::string albumpath = m_song->GetProperty("album_path").asString(); if (!albumpath.empty()) { CFileItem pathItem(albumpath, true); CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(sources, pathItem); } else // Add parent folder of song CGUIDialogMusicInfo::AddItemPathToFileBrowserSources(sources, *m_song); 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, or the fallback image. // Overwrite with the new art or clear it std::string newArt; if (result == "thumb://Thumb") newArt = m_song->GetArt("thumb"); else if (result == "thumb://Local") newArt = localThumb; // else if (result == "thumb://Embedded") // newArt = embeddedArt; else if (CFileUtils::Exists(result)) newArt = result; else // none newArt.clear(); // Asynchronously update that type of art in the database MUSIC_UTILS::UpdateArtJob(m_song, type, newArt); // Update local song with current art if (newArt.empty()) { // Remove that type of art from the song primeArt.erase(type); m_song->SetArt(primeArt); } else // Add or modify the type of art m_song->SetArt(type, newArt); // Update local art list with current art // Show any fallback art when song art removed if (newArt.empty() && m_song->HasArt(type)) newArt = m_song->GetArt(type); 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, // current playlist and player OSD. CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_song); CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); } // Re-open the art type selection dialog as we come back from // the image selection dialog OnGetArt(); } void CGUIDialogSongInfo::OnSetUserrating() { int userrating = MUSIC_UTILS::ShowSelectRatingDialog(m_song->GetMusicInfoTag()->GetUserrating()); if (userrating < 0) // Nothing selected, so rating unchanged return; SetUserrating(userrating); } void CGUIDialogSongInfo::ShowFor(CFileItem* pItem) { if (pItem->m_bIsFolder) return; if (!pItem->IsMusicDb()) pItem->LoadMusicTag(); if (!pItem->HasMusicInfoTag()) return; CGUIDialogSongInfo *dialog = CServiceBroker::GetGUI()->GetWindowManager(). GetWindow(WINDOW_DIALOG_SONG_INFO); if (dialog) { if (dialog->SetSong(pItem)) // Fetch full song info asynchronously { dialog->Open(); if (dialog->HasUpdatedUserrating()) { auto window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_MUSIC_NAV); if (window) window->RefreshContent("songs"); } } } } void CGUIDialogSongInfo::OnPlaySong(const std::shared_ptr& item) { Close(true); MUSIC_UTILS::PlayItem(item); }