From c04dcc2e7d834218ef2d4194331e383402495ae1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 20:07:22 +0200 Subject: Adding upstream version 2:20.4+dfsg. Signed-off-by: Daniel Baumann --- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 2398 +++++++++++++++++++++++++++++ 1 file changed, 2398 insertions(+) create mode 100644 xbmc/video/dialogs/GUIDialogVideoInfo.cpp (limited to 'xbmc/video/dialogs/GUIDialogVideoInfo.cpp') diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp new file mode 100644 index 0000000..240d672 --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -0,0 +1,2398 @@ +/* + * 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 "GUIDialogVideoInfo.h" + +#include "ContextMenuManager.h" +#include "FileItem.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "Util.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "dialogs/GUIDialogProgress.h" +#include "dialogs/GUIDialogSelect.h" +#include "dialogs/GUIDialogYesNo.h" +#include "filesystem/Directory.h" +#include "filesystem/VideoDatabaseDirectory.h" +#include "filesystem/VideoDatabaseDirectory/QueryParams.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIImage.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindow.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/Key.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "music/MusicDatabase.h" +#include "music/dialogs/GUIDialogMusicInfo.h" +#include "playlists/PlayListTypes.h" +#include "profiles/ProfileManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/SettingUtils.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "storage/MediaManager.h" +#include "utils/FileExtensionProvider.h" +#include "utils/FileUtils.h" +#include "utils/SortUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "video/VideoDbUrl.h" +#include "video/VideoInfoScanner.h" +#include "video/VideoInfoTag.h" +#include "video/VideoLibraryQueue.h" +#include "video/VideoThumbLoader.h" +#include "video/tags/VideoTagLoaderFFmpeg.h" +#include "video/windows/GUIWindowVideoNav.h" + +#include +#include + +using namespace XFILE::VIDEODATABASEDIRECTORY; +using namespace XFILE; +using namespace KODI::MESSAGING; + +#define CONTROL_IMAGE 3 +#define CONTROL_TEXTAREA 4 +#define CONTROL_BTN_TRACKS 5 +#define CONTROL_BTN_REFRESH 6 +#define CONTROL_BTN_USERRATING 7 +#define CONTROL_BTN_PLAY 8 +#define CONTROL_BTN_RESUME 9 +#define CONTROL_BTN_GET_THUMB 10 +#define CONTROL_BTN_PLAY_TRAILER 11 +#define CONTROL_BTN_GET_FANART 12 +#define CONTROL_BTN_DIRECTOR 13 + +#define CONTROL_LIST 50 + +// predicate used by sorting and set_difference +bool compFileItemsByDbId(const CFileItemPtr& lhs, const CFileItemPtr& rhs) +{ + return lhs->HasVideoInfoTag() && rhs->HasVideoInfoTag() && lhs->GetVideoInfoTag()->m_iDbId < rhs->GetVideoInfoTag()->m_iDbId; +} + +CGUIDialogVideoInfo::CGUIDialogVideoInfo(void) + : CGUIDialog(WINDOW_DIALOG_VIDEO_INFO, "DialogVideoInfo.xml"), + m_movieItem(new CFileItem), + m_castList(new CFileItemList) +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogVideoInfo::~CGUIDialogVideoInfo(void) +{ + delete m_castList; +} + +bool CGUIDialogVideoInfo::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_DEINIT: + { + ClearCastList(); + + if (m_startUserrating != m_movieItem->GetVideoInfoTag()->m_iUserRating) + { + CVideoDatabase db; + if (db.Open()) + { + m_hasUpdatedUserrating = true; + db.SetVideoUserRating(m_movieItem->GetVideoInfoTag()->m_iDbId, m_movieItem->GetVideoInfoTag()->m_iUserRating, m_movieItem->GetVideoInfoTag()->m_type); + db.Close(); + } + } + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_BTN_REFRESH) + { + if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeTvShow) + { + bool bCanceled=false; + if (CGUIDialogYesNo::ShowAndGetInput(CVariant{20377}, CVariant{20378}, bCanceled, CVariant{ "" }, CVariant{ "" }, CGUIDialogYesNo::NO_TIMEOUT)) + { + m_bRefreshAll = true; + CVideoDatabase db; + if (db.Open()) + { + db.SetPathHash(m_movieItem->GetVideoInfoTag()->m_strPath,""); + db.Close(); + } + } + else + m_bRefreshAll = false; + + if (bCanceled) + return false; + } + m_bRefresh = true; + Close(); + return true; + } + else if (iControl == CONTROL_BTN_TRACKS) + { + m_bViewReview = !m_bViewReview; + Update(); + } + else if (iControl == CONTROL_BTN_PLAY) + { + Play(); + } + else if (iControl == CONTROL_BTN_USERRATING) + { + OnSetUserrating(); + } + else if (iControl == CONTROL_BTN_RESUME) + { + Play(true); + } + else if (iControl == CONTROL_BTN_GET_THUMB) + { + OnGetArt(); + } + else if (iControl == CONTROL_BTN_PLAY_TRAILER) + { + PlayTrailer(); + } + else if (iControl == CONTROL_BTN_GET_FANART) + { + OnGetFanart(); + } + else if (iControl == CONTROL_BTN_DIRECTOR) + { + auto directors = m_movieItem->GetVideoInfoTag()->m_director; + if (directors.size() == 0) + return true; + if (directors.size() == 1) + OnSearch(directors[0]); + else + { + auto pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); + if (pDlgSelect) + { + pDlgSelect->Reset(); + pDlgSelect->SetHeading(CVariant{22080}); + for (const auto &director: directors) + pDlgSelect->Add(director); + pDlgSelect->Open(); + + int iItem = pDlgSelect->GetSelectedItem(); + if (iItem < 0) + return true; + OnSearch(directors[iItem]); + } + } + } + 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); + OnMessage(msg); + int iItem = msg.GetParam1(); + if (iItem < 0 || iItem >= m_castList->Size()) + break; + std::string strItem = m_castList->Get(iItem)->GetLabel(); + if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeVideoCollection) + { + SetMovie(m_castList->Get(iItem).get()); + Close(); + Open(); + } + else + OnSearch(strItem); + } + } + } + break; + case GUI_MSG_NOTIFY_ALL: + { + if (IsActive() && message.GetParam1() == GUI_MSG_UPDATE_ITEM && message.GetItem()) + { + CFileItemPtr item = std::static_pointer_cast(message.GetItem()); + if (item && m_movieItem->IsPath(item->GetPath())) + { // Just copy over the stream details and the thumb if we don't already have one + if (!m_movieItem->HasArt("thumb")) + m_movieItem->SetArt("thumb", item->GetArt("thumb")); + m_movieItem->GetVideoInfoTag()->m_streamDetails = item->GetVideoInfoTag()->m_streamDetails; + } + return true; + } + } + } + + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogVideoInfo::OnInitWindow() +{ + m_bRefresh = false; + m_bRefreshAll = true; + m_hasUpdatedThumb = false; + m_hasUpdatedUserrating = false; + m_bViewReview = true; + + const std::shared_ptr profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + const std::string uniqueId = m_movieItem->GetProperty("xxuniqueid").asString(); + if (uniqueId.empty() || !StringUtils::StartsWithNoCase(uniqueId.c_str(), "xx")) + CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_REFRESH, + (profileManager->GetCurrentProfile().canWriteDatabases() || + g_passwordManager.bMasterUser)); + else + CONTROL_DISABLE(CONTROL_BTN_REFRESH); + CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_GET_THUMB, + (profileManager->GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser) && + !StringUtils::StartsWithNoCase(m_movieItem->GetVideoInfoTag()-> + GetUniqueID().c_str(), "plugin")); + // Disable video user rating button for plugins and sets as they don't have tables to save this + CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_USERRATING, !m_movieItem->IsPlugin() && m_movieItem->GetVideoInfoTag()->m_type != MediaTypeVideoCollection); + + VideoDbContentType type = m_movieItem->GetVideoContentType(); + if (type == VideoDbContentType::TVSHOWS || type == VideoDbContentType::MOVIES) + CONTROL_ENABLE_ON_CONDITION(CONTROL_BTN_GET_FANART, (profileManager-> + GetCurrentProfile().canWriteDatabases() || g_passwordManager.bMasterUser) && + !StringUtils::StartsWithNoCase(m_movieItem->GetVideoInfoTag()-> + GetUniqueID().c_str(), "plugin")); + else + CONTROL_DISABLE(CONTROL_BTN_GET_FANART); + + Update(); + + CGUIDialog::OnInitWindow(); +} + +bool CGUIDialogVideoInfo::OnAction(const CAction &action) +{ + int userrating = m_movieItem->GetVideoInfoTag()->m_iUserRating; + 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); +} + +void CGUIDialogVideoInfo::SetUserrating(int userrating) const +{ + userrating = std::max(userrating, 0); + userrating = std::min(userrating, 10); + if (userrating != m_movieItem->GetVideoInfoTag()->m_iUserRating) + { + m_movieItem->GetVideoInfoTag()->SetUserrating(userrating); + + // send a message to all windows to tell them to update the fileitem (eg playlistplayer, media windows) + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, m_movieItem); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } +} + +void CGUIDialogVideoInfo::SetMovie(const CFileItem *item) +{ + *m_movieItem = *item; + + // setup cast list + ClearCastList(); + + // When the scraper throws an error, the video tag can be null here + if (!item->HasVideoInfoTag()) + return; + + MediaType type = item->GetVideoInfoTag()->m_type; + + m_startUserrating = m_movieItem->GetVideoInfoTag()->m_iUserRating; + + if (type == MediaTypeMusicVideo) + { // music video + CMusicDatabase database; + database.Open(); + const std::vector &artists = m_movieItem->GetVideoInfoTag()->m_artist; + for (std::vector::const_iterator it = artists.begin(); it != artists.end(); ++it) + { + int idArtist = database.GetArtistByName(*it); + std::string thumb = database.GetArtForItem(idArtist, MediaTypeArtist, "thumb"); + CFileItemPtr item(new CFileItem(*it)); + if (!thumb.empty()) + item->SetArt("thumb", thumb); + item->SetArt("icon", "DefaultArtist.png"); + item->SetLabel2(g_localizeStrings.Get(29904)); + m_castList->Add(item); + } + // get performers in the music video (added as actors) + for (CVideoInfoTag::iCast it = m_movieItem->GetVideoInfoTag()->m_cast.begin(); + it != m_movieItem->GetVideoInfoTag()->m_cast.end(); ++it) + { + // Check to see if we have already added this performer as the artist and skip adding if so + auto haveArtist = std::find(std::begin(artists), std::end(artists), it->strName); + if (haveArtist == artists.end()) // artist or performer not already in the list + { + CFileItemPtr item(new CFileItem(it->strName)); + if (!it->thumb.empty()) + item->SetArt("thumb", it->thumb); + else if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_VIDEOLIBRARY_ACTORTHUMBS)) + { // backward compatibility + std::string thumb = CScraperUrl::GetThumbUrl(it->thumbUrl.GetFirstUrlByType()); + if (!thumb.empty()) + { + item->SetArt("thumb", thumb); + CServiceBroker::GetTextureCache()->BackgroundCacheImage(thumb); + } + } + item->SetArt("icon", "DefaultActor.png"); + item->SetLabel(it->strName); + item->SetLabel2(it->strRole); + m_castList->Add(item); + } + } + } + else if (type == MediaTypeVideoCollection) + { + CVideoDatabase database; + database.Open(); + database.GetMoviesNav(m_movieItem->GetPath(), *m_castList, -1, -1, -1, -1, -1, -1, + m_movieItem->GetVideoInfoTag()->m_set.id, -1, + SortDescription(), VideoDbDetailsAll); + m_castList->Sort(SortBySortTitle, SortOrderDescending); + CVideoThumbLoader loader; + for (auto& item : *m_castList) + loader.LoadItem(item.get()); + } + else + { // movie/show/episode + for (CVideoInfoTag::iCast it = m_movieItem->GetVideoInfoTag()->m_cast.begin(); it != m_movieItem->GetVideoInfoTag()->m_cast.end(); ++it) + { + CFileItemPtr item(new CFileItem(it->strName)); + if (!it->thumb.empty()) + item->SetArt("thumb", it->thumb); + else + { + const std::shared_ptr settings = + CServiceBroker::GetSettingsComponent()->GetSettings(); + if (settings->GetInt(CSettings::SETTING_VIDEOLIBRARY_ARTWORK_LEVEL) != + CSettings::VIDEOLIBRARY_ARTWORK_LEVEL_NONE && + settings->GetBool(CSettings::SETTING_VIDEOLIBRARY_ACTORTHUMBS)) + { // backward compatibility + std::string thumb = CScraperUrl::GetThumbUrl(it->thumbUrl.GetFirstUrlByType()); + if (!thumb.empty()) + { + item->SetArt("thumb", thumb); + CServiceBroker::GetTextureCache()->BackgroundCacheImage(thumb); + } + } + } + item->SetArt("icon", "DefaultActor.png"); + item->SetLabel(it->strName); + item->SetLabel2(it->strRole); + m_castList->Add(item); + } + } + + if (type == MediaTypeMovie) + { + // local trailers should always override non-local, so check + // for a local one if the registered trailer is online + if (m_movieItem->GetVideoInfoTag()->m_strTrailer.empty() || + URIUtils::IsInternetStream(m_movieItem->GetVideoInfoTag()->m_strTrailer)) + { + std::string localTrailer = m_movieItem->FindTrailer(); + if (!localTrailer.empty()) + { + m_movieItem->GetVideoInfoTag()->m_strTrailer = localTrailer; + CVideoDatabase database; + if (database.Open()) + { + database.SetSingleValue(VideoDbContentType::MOVIES, VIDEODB_ID_TRAILER, + m_movieItem->GetVideoInfoTag()->m_iDbId, + m_movieItem->GetVideoInfoTag()->m_strTrailer); + database.Close(); + CUtil::DeleteVideoDatabaseDirectoryCache(); + } + } + } + } + + m_castList->SetContent(CMediaTypes::ToPlural(type)); + + CVideoThumbLoader loader; + loader.LoadItem(m_movieItem.get()); +} + +void CGUIDialogVideoInfo::Update() +{ + // setup plot text area + std::shared_ptr setting(std::dynamic_pointer_cast( + CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(CSettings::SETTING_VIDEOLIBRARY_SHOWUNWATCHEDPLOTS))); + std::string strTmp = m_movieItem->GetVideoInfoTag()->m_strPlot; + if (m_movieItem->GetVideoInfoTag()->m_type != MediaTypeTvShow) + if (m_movieItem->GetVideoInfoTag()->GetPlayCount() == 0 && setting && + ((m_movieItem->GetVideoInfoTag()->m_type == MediaTypeMovie && + !CSettingUtils::FindIntInList(setting, + CSettings::VIDEOLIBRARY_PLOTS_SHOW_UNWATCHED_MOVIES)) || + (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeEpisode && + !CSettingUtils::FindIntInList( + setting, CSettings::VIDEOLIBRARY_PLOTS_SHOW_UNWATCHED_TVSHOWEPISODES)))) + strTmp = g_localizeStrings.Get(20370); + + StringUtils::Trim(strTmp); + SetLabel(CONTROL_TEXTAREA, strTmp); + + CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), CONTROL_LIST, 0, 0, m_castList); + OnMessage(msg); + + if (GetControl(CONTROL_BTN_TRACKS)) // if no CONTROL_BTN_TRACKS found - allow skinner full visibility control over CONTROL_TEXTAREA and CONTROL_LIST + { + if (m_bViewReview) + { + if (!m_movieItem->GetVideoInfoTag()->m_artist.empty()) + { + SET_CONTROL_LABEL(CONTROL_BTN_TRACKS, 133); + } + else if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeVideoCollection) + { + SET_CONTROL_LABEL(CONTROL_BTN_TRACKS, 20342); + } + else + { + SET_CONTROL_LABEL(CONTROL_BTN_TRACKS, 206); + } + + SET_CONTROL_HIDDEN(CONTROL_LIST); + SET_CONTROL_VISIBLE(CONTROL_TEXTAREA); + } + else + { + SET_CONTROL_LABEL(CONTROL_BTN_TRACKS, 207); + + SET_CONTROL_HIDDEN(CONTROL_TEXTAREA); + SET_CONTROL_VISIBLE(CONTROL_LIST); + } + } + + // Check for resumability + if (m_movieItem->GetVideoInfoTag()->GetResumePoint().timeInSeconds > 0.0) + CONTROL_ENABLE(CONTROL_BTN_RESUME); + else + CONTROL_DISABLE(CONTROL_BTN_RESUME); + + CONTROL_ENABLE(CONTROL_BTN_PLAY); + + // update the thumbnail + CGUIControl* pControl = GetControl(CONTROL_IMAGE); + if (pControl) + { + CGUIImage* pImageControl = static_cast(pControl); + pImageControl->FreeResources(); + pImageControl->SetFileName(m_movieItem->GetArt("thumb")); + } + // tell our GUI to completely reload all controls (as some of them + // are likely to have had this image in use so will need refreshing) + if (m_hasUpdatedThumb) + { + CGUIMessage reload(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REFRESH_THUMBS); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(reload); + } +} + +bool CGUIDialogVideoInfo::NeedRefresh() const +{ + return m_bRefresh; +} + +bool CGUIDialogVideoInfo::RefreshAll() const +{ + return m_bRefreshAll; +} + +void CGUIDialogVideoInfo::OnSearch(std::string& strSearch) +{ + CGUIDialogProgress *progress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_PROGRESS); + if (progress) + { + progress->SetHeading(CVariant{194}); + progress->SetLine(0, CVariant{strSearch}); + progress->SetLine(1, CVariant{""}); + progress->SetLine(2, CVariant{""}); + progress->Open(); + progress->Progress(); + } + CFileItemList items; + DoSearch(strSearch, items); + + if (progress) + progress->Close(); + + if (items.Size()) + { + CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); + if (pDlgSelect) + { + pDlgSelect->Reset(); + pDlgSelect->SetHeading(CVariant{283}); + + CVideoThumbLoader loader; + for (int i = 0; i < items.Size(); i++) + { + if (items[i]->HasVideoInfoTag() && + items[i]->GetVideoInfoTag()->GetPlayCount() > 0) + items[i]->SetLabel2(g_localizeStrings.Get(16102)); + + loader.LoadItem(items[i].get()); + pDlgSelect->Add(*items[i]); + } + + pDlgSelect->SetUseDetails(true); + pDlgSelect->Open(); + + int iItem = pDlgSelect->GetSelectedItem(); + if (iItem < 0) + return; + + OnSearchItemFound(items[iItem].get()); + } + } + else + { + HELPERS::ShowOKDialogText(CVariant{194}, CVariant{284}); + } +} + +void CGUIDialogVideoInfo::DoSearch(std::string& strSearch, CFileItemList& items) const +{ + CVideoDatabase db; + if (!db.Open()) + return; + + CFileItemList movies; + db.GetMoviesByActor(strSearch, movies); + for (int i = 0; i < movies.Size(); ++i) + { + std::string label = movies[i]->GetVideoInfoTag()->m_strTitle; + if (movies[i]->GetVideoInfoTag()->HasYear()) + label += StringUtils::Format(" ({})", movies[i]->GetVideoInfoTag()->GetYear()); + movies[i]->SetLabel(label); + } + CGUIWindowVideoBase::AppendAndClearSearchItems(movies, "[" + g_localizeStrings.Get(20338) + "] ", items); + + db.GetTvShowsByActor(strSearch, movies); + for (int i = 0; i < movies.Size(); ++i) + { + std::string label = movies[i]->GetVideoInfoTag()->m_strShowTitle; + if (movies[i]->GetVideoInfoTag()->HasYear()) + label += StringUtils::Format(" ({})", movies[i]->GetVideoInfoTag()->GetYear()); + movies[i]->SetLabel(label); + } + CGUIWindowVideoBase::AppendAndClearSearchItems(movies, "[" + g_localizeStrings.Get(20364) + "] ", items); + + db.GetEpisodesByActor(strSearch, movies); + for (int i = 0; i < movies.Size(); ++i) + { + std::string label = movies[i]->GetVideoInfoTag()->m_strTitle + " (" + movies[i]->GetVideoInfoTag()->m_strShowTitle + ")"; + movies[i]->SetLabel(label); + } + CGUIWindowVideoBase::AppendAndClearSearchItems(movies, "[" + g_localizeStrings.Get(20359) + "] ", items); + + db.GetMusicVideosByArtist(strSearch, movies); + for (int i = 0; i < movies.Size(); ++i) + { + std::string label = StringUtils::Join(movies[i]->GetVideoInfoTag()->m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + " - " + movies[i]->GetVideoInfoTag()->m_strTitle; + if (movies[i]->GetVideoInfoTag()->HasYear()) + label += StringUtils::Format(" ({})", movies[i]->GetVideoInfoTag()->GetYear()); + movies[i]->SetLabel(label); + } + CGUIWindowVideoBase::AppendAndClearSearchItems(movies, "[" + g_localizeStrings.Get(20391) + "] ", items); + db.Close(); + + // Search for music albums by artist with name matching search string + CMusicDatabase music_database; + if (!music_database.Open()) + return; + + if (music_database.SearchAlbumsByArtistName(strSearch, movies)) + { + for (int i = 0; i < movies.Size(); ++i) + { + // Set type so that video thumbloader handles album art + movies[i]->GetVideoInfoTag()->m_type = MediaTypeAlbum; + } + CGUIWindowVideoBase::AppendAndClearSearchItems( + movies, "[" + g_localizeStrings.Get(36918) + "] ", items); + } + music_database.Close(); +} + +void CGUIDialogVideoInfo::OnSearchItemFound(const CFileItem* pItem) +{ + VideoDbContentType type = pItem->GetVideoContentType(); + + CVideoDatabase db; + if (!db.Open()) + return; + + CVideoInfoTag movieDetails; + if (type == VideoDbContentType::MOVIES) + db.GetMovieInfo(pItem->GetPath(), movieDetails, pItem->GetVideoInfoTag()->m_iDbId); + if (type == VideoDbContentType::EPISODES) + db.GetEpisodeInfo(pItem->GetPath(), movieDetails, pItem->GetVideoInfoTag()->m_iDbId); + if (type == VideoDbContentType::TVSHOWS) + db.GetTvShowInfo(pItem->GetPath(), movieDetails, pItem->GetVideoInfoTag()->m_iDbId); + if (type == VideoDbContentType::MUSICVIDEOS) + db.GetMusicVideoInfo(pItem->GetPath(), movieDetails, pItem->GetVideoInfoTag()->m_iDbId); + db.Close(); + if (type == VideoDbContentType::MUSICALBUMS) + { + Close(); + CGUIDialogMusicInfo::ShowFor(const_cast(pItem)); + return; // No video info to refresh so just close the window and go back to the fileitem list + } + + CFileItem item(*pItem); + *item.GetVideoInfoTag() = movieDetails; + SetMovie(&item); + // refresh our window entirely + Close(); + Open(); +} + +void CGUIDialogVideoInfo::ClearCastList() +{ + CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_LIST); + OnMessage(msg); + m_castList->Clear(); +} + +void CGUIDialogVideoInfo::Play(bool resume) +{ + if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeTvShow) + { + std::string strPath; + if (m_movieItem->IsPlugin()) + { + strPath = m_movieItem->GetPath(); + Close(); + if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_NAV) + { + CGUIMessage message(GUI_MSG_NOTIFY_ALL, CServiceBroker::GetGUI()-> + GetWindowManager().GetActiveWindow(), 0, GUI_MSG_UPDATE, 0); + message.SetStringParam(strPath); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + } + else + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV,strPath); + } + else + { + strPath = StringUtils::Format("videodb://tvshows/titles/{}/", + m_movieItem->GetVideoInfoTag()->m_iDbId); + Close(); + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV,strPath); + } + return; + } + + if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeVideoCollection) + { + std::string strPath = StringUtils::Format("videodb://movies/sets/{}/?setid={}", + m_movieItem->GetVideoInfoTag()->m_iDbId, + m_movieItem->GetVideoInfoTag()->m_iDbId); + Close(); + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV, strPath); + return; + } + + CGUIWindowVideoNav* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_VIDEO_NAV); + if (pWindow) + { + // close our dialog + Close(true); + if (resume) + m_movieItem->SetStartOffset(STARTOFFSET_RESUME); + else if (!CGUIWindowVideoBase::ShowResumeMenu(*m_movieItem)) + { + // The Resume dialog was closed without any choice + Open(); + return; + } + m_movieItem->SetProperty("playlist_type_hint", PLAYLIST::TYPE_VIDEO); + + pWindow->PlayMovie(m_movieItem.get()); + } +} + +namespace +{ +// Add art types required in Kodi and configured by the user +void AddHardCodedAndExtendedArtTypes(std::vector& artTypes, const CVideoInfoTag& tag) +{ + for (const auto& artType : CVideoThumbLoader::GetArtTypes(tag.m_type)) + { + if (find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend()) + artTypes.emplace_back(artType); + } +} + +// Add art types currently assigned to the media item +void AddCurrentArtTypes(std::vector& artTypes, const CVideoInfoTag& tag, + CVideoDatabase& db) +{ + std::map currentArt; + db.GetArtForItem(tag.m_iDbId, tag.m_type, currentArt); + for (const auto& art : currentArt) + { + if (!art.second.empty() && find(artTypes.cbegin(), artTypes.cend(), art.first) == artTypes.cend()) + artTypes.push_back(art.first); + } +} + +// Add art types that exist for other media items of the same type +void AddMediaTypeArtTypes(std::vector& artTypes, const CVideoInfoTag& tag, + CVideoDatabase& db) +{ + std::vector dbArtTypes; + db.GetArtTypes(tag.m_type, dbArtTypes); + for (const auto& artType : dbArtTypes) + { + if (find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend()) + artTypes.push_back(artType); + } +} + +// Add art types from available but unassigned artwork for this media item +void AddAvailableArtTypes(std::vector& artTypes, const CVideoInfoTag& tag, + CVideoDatabase& db) +{ + for (const auto& artType : db.GetAvailableArtTypesForItem(tag.m_iDbId, tag.m_type)) + { + if (find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend()) + artTypes.push_back(artType); + } +} + +std::vector GetArtTypesList(const CVideoInfoTag& tag) +{ + CVideoDatabase db; + db.Open(); + + std::vector artTypes; + + AddHardCodedAndExtendedArtTypes(artTypes, tag); + AddCurrentArtTypes(artTypes, tag, db); + AddMediaTypeArtTypes(artTypes, tag, db); + AddAvailableArtTypes(artTypes, tag, db); + + db.Close(); + return artTypes; +} +} + +std::string CGUIDialogVideoInfo::ChooseArtType(const CFileItem &videoItem) +{ + // prompt for choice + CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); + if (!dialog || !videoItem.HasVideoInfoTag()) + return ""; + + CFileItemList items; + dialog->SetHeading(CVariant{13511}); + dialog->Reset(); + dialog->SetUseDetails(true); + dialog->EnableButton(true, 13516); + + std::vector artTypes = GetArtTypesList(*videoItem.GetVideoInfoTag()); + + for (std::vector::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i) + { + const std::string& type = *i; + CFileItemPtr item(new CFileItem(type, false)); + if (type == "banner") + item->SetLabel(g_localizeStrings.Get(20020)); + else if (type == "fanart") + item->SetLabel(g_localizeStrings.Get(20445)); + else if (type == "poster") + item->SetLabel(g_localizeStrings.Get(20021)); + else if (type == "thumb") + item->SetLabel(g_localizeStrings.Get(21371)); + else + item->SetLabel(type); + item->SetProperty("type", type); + if (videoItem.HasArt(type)) + item->SetArt("thumb", videoItem.GetArt(type)); + items.Add(item); + } + + dialog->SetItems(items); + dialog->Open(); + + if (dialog->IsButtonPressed()) + { + // Get the new artwork name + std::string strArtworkName; + if (!CGUIKeyboardFactory::ShowAndGetInput(strArtworkName, CVariant{g_localizeStrings.Get(13516)}, false)) + return ""; + + return strArtworkName; + } + + return dialog->GetSelectedFileItem()->GetProperty("type").asString(); +} + +void CGUIDialogVideoInfo::OnGetArt() +{ + if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeVideoCollection) + { + ManageVideoItemArtwork(m_movieItem, m_movieItem->GetVideoInfoTag()->m_type); + return; + } + std::string type = ChooseArtType(*m_movieItem); + if (type.empty()) + return; // cancelled + + //! @todo this can be removed once these are unified. + if (type == "fanart") + OnGetFanart(); + else + { + CFileItemList items; + + // Current thumb + if (m_movieItem->HasArt(type)) + { + CFileItemPtr item(new CFileItem("thumb://Current", false)); + item->SetArt("thumb", m_movieItem->GetArt(type)); + item->SetArt("icon", "DefaultPicture.png"); + item->SetLabel(g_localizeStrings.Get(13512)); + items.Add(item); + } + else if ((type == "poster" || type == "banner") && m_movieItem->HasArt("thumb")) + { // add the 'thumb' type in + CFileItemPtr item(new CFileItem("thumb://Thumb", false)); + item->SetArt("thumb", m_movieItem->GetArt("thumb")); + item->SetArt("icon", "DefaultPicture.png"); + item->SetLabel(g_localizeStrings.Get(13512)); + items.Add(item); + } + + std::string embeddedArt; + if (URIUtils::HasExtension(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, ".mkv")) + { + CFileItem item(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, false); + CVideoTagLoaderFFmpeg loader(item, nullptr, false); + CVideoInfoTag tag; + loader.Load(tag, false, nullptr); + for (const auto& it : tag.m_coverArt) + { + if (it.m_type == type) + { + CFileItemPtr itemF(new CFileItem("thumb://Embedded", false)); + embeddedArt = CTextureUtils::GetWrappedImageURL(item.GetPath(), "video_" + type); + itemF->SetArt("thumb", embeddedArt); + itemF->SetLabel(g_localizeStrings.Get(13519)); + items.Add(itemF); + } + } + } + + // Grab the thumbnails from the web + m_movieItem->GetVideoInfoTag()->m_strPictureURL.Parse(); + std::vector thumbs; + int season = (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeSeason) ? m_movieItem->GetVideoInfoTag()->m_iSeason : -1; + m_movieItem->GetVideoInfoTag()->m_strPictureURL.GetThumbUrls(thumbs, type, season); + + for (unsigned int i = 0; i < thumbs.size(); ++i) + { + std::string strItemPath = StringUtils::Format("thumb://Remote{}", i); + CFileItemPtr item(new CFileItem(strItemPath, false)); + item->SetArt("thumb", thumbs[i]); + item->SetArt("icon", "DefaultPicture.png"); + item->SetLabel(g_localizeStrings.Get(13513)); + + //! @todo Do we need to clear the cached image? + // CServiceBroker::GetTextureCache()->ClearCachedImage(thumb); + items.Add(item); + } + + std::string localThumb = CVideoThumbLoader::GetLocalArt(*m_movieItem, type); + if (!localThumb.empty()) + { + CFileItemPtr item(new CFileItem("thumb://Local", false)); + item->SetArt("thumb", localThumb); + item->SetArt("icon", "DefaultPicture.png"); + item->SetLabel(g_localizeStrings.Get(13514)); + items.Add(item); + } + else + { // no local thumb exists, so we are just using the IMDb thumb or cached thumb + // which is probably the IMDb thumb. These could be wrong, so allow the user + // to delete the incorrect thumb + CFileItemPtr item(new CFileItem("thumb://None", false)); + item->SetArt("icon", "DefaultPicture.png"); + item->SetLabel(g_localizeStrings.Get(13515)); + items.Add(item); + } + + std::string result; + VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("video")); + AddItemPathToFileBrowserSources(sources, *m_movieItem); + 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 + { + std::string newThumb; + if (StringUtils::StartsWith(result, "thumb://Remote")) + { + int number = atoi(result.substr(14).c_str()); + newThumb = thumbs[number]; + } + else if (result == "thumb://Thumb") + newThumb = m_movieItem->GetArt("thumb"); + else if (result == "thumb://Local") + newThumb = localThumb; + else if (result == "thumb://Embedded") + newThumb = embeddedArt; + else if (CFileUtils::Exists(result)) + newThumb = result; + else // none + newThumb.clear(); + + // update thumb in the database + CVideoDatabase db; + if (db.Open()) + { + db.SetArtForItem(m_movieItem->GetVideoInfoTag()->m_iDbId, m_movieItem->GetVideoInfoTag()->m_type, type, newThumb); + db.Close(); + } + CUtil::DeleteVideoDatabaseDirectoryCache(); // to get them new thumbs to show + m_movieItem->SetArt(type, newThumb); + if (m_movieItem->HasProperty("set_folder_thumb")) + { // have a folder thumb to set as well + VIDEO::CVideoInfoScanner::ApplyThumbToFolder(m_movieItem->GetProperty("set_folder_thumb").asString(), newThumb); + } + m_hasUpdatedThumb = true; + } + } + + // Update our screen + Update(); + + // re-open the art selection dialog as we come back from + // the image selection dialog + OnGetArt(); +} + +// Allow user to select a Fanart +void CGUIDialogVideoInfo::OnGetFanart() +{ + CFileItemList items; + + // Ensure the fanart is unpacked + m_movieItem->GetVideoInfoTag()->m_fanart.Unpack(); + + if (m_movieItem->HasArt("fanart")) + { + CFileItemPtr itemCurrent(new CFileItem("fanart://Current",false)); + itemCurrent->SetArt("thumb", m_movieItem->GetArt("fanart")); + itemCurrent->SetArt("icon", "DefaultPicture.png"); + itemCurrent->SetLabel(g_localizeStrings.Get(20440)); + items.Add(itemCurrent); + } + + std::string embeddedArt; + if (URIUtils::HasExtension(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, ".mkv")) + { + CFileItem item(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, false); + CVideoTagLoaderFFmpeg loader(item, nullptr, false); + CVideoInfoTag tag; + loader.Load(tag, false, nullptr); + for (const auto& it : tag.m_coverArt) + { + if (it.m_type == "fanart") + { + CFileItemPtr itemF(new CFileItem("fanart://Embedded", false)); + embeddedArt = CTextureUtils::GetWrappedImageURL(item.GetPath(), "video_fanart"); + itemF->SetArt("thumb", embeddedArt); + itemF->SetLabel(g_localizeStrings.Get(13520)); + items.Add(itemF); + } + } + } + + // Grab the thumbnails from the web + for (unsigned int i = 0; i < m_movieItem->GetVideoInfoTag()->m_fanart.GetNumFanarts(); i++) + { + if (URIUtils::IsProtocol(m_movieItem->GetVideoInfoTag()->m_fanart.GetPreviewURL(i), "image")) + continue; + std::string strItemPath = StringUtils::Format("fanart://Remote{}", i); + CFileItemPtr item(new CFileItem(strItemPath, false)); + std::string thumb = m_movieItem->GetVideoInfoTag()->m_fanart.GetPreviewURL(i); + item->SetArt("thumb", CTextureUtils::GetWrappedThumbURL(thumb)); + item->SetArt("icon", "DefaultPicture.png"); + item->SetLabel(g_localizeStrings.Get(20441)); + + //! @todo Do we need to clear the cached image? + // CServiceBroker::GetTextureCache()->ClearCachedImage(thumb); + items.Add(item); + } + + CFileItem item(*m_movieItem->GetVideoInfoTag()); + std::string strLocal = item.GetLocalFanart(); + if (!strLocal.empty()) + { + CFileItemPtr itemLocal(new CFileItem("fanart://Local",false)); + itemLocal->SetArt("thumb", strLocal); + itemLocal->SetArt("icon", "DefaultPicture.png"); + itemLocal->SetLabel(g_localizeStrings.Get(20438)); + + //! @todo Do we need to clear the cached image? + CServiceBroker::GetTextureCache()->ClearCachedImage(strLocal); + items.Add(itemLocal); + } + else + { + CFileItemPtr itemNone(new CFileItem("fanart://None", false)); + itemNone->SetArt("icon", "DefaultPicture.png"); + itemNone->SetLabel(g_localizeStrings.Get(20439)); + items.Add(itemNone); + } + + std::string result; + VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("video")); + AddItemPathToFileBrowserSources(sources, item); + CServiceBroker::GetMediaManager().GetLocalDrives(sources); + bool flip=false; + if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(20437), result, &flip, 20445) || + StringUtils::EqualsNoCase(result, "fanart://Current")) + return; // user cancelled + + if (StringUtils::EqualsNoCase(result, "fanart://Local")) + result = strLocal; + + if (StringUtils::EqualsNoCase(result, "fanart://Embedded")) + { + unsigned int current = m_movieItem->GetVideoInfoTag()->m_fanart.GetNumFanarts(); + int found = -1; + for (size_t i = 0; i < current; ++i) + if (URIUtils::IsProtocol(m_movieItem->GetVideoInfoTag()->m_fanart.GetImageURL(), "image")) + found = i; + if (found != -1) + { + m_movieItem->GetVideoInfoTag()->m_fanart.AddFanart(embeddedArt, "", ""); + found = current; + } + + m_movieItem->GetVideoInfoTag()->m_fanart.SetPrimaryFanart(found); + + CVideoDatabase db; + if (db.Open()) + { + db.UpdateFanart(*m_movieItem, m_movieItem->GetVideoContentType()); + db.Close(); + } + result = embeddedArt; + } + + if (StringUtils::StartsWith(result, "fanart://Remote")) + { + int iFanart = atoi(result.substr(15).c_str()); + // set new primary fanart, and update our database accordingly + m_movieItem->GetVideoInfoTag()->m_fanart.SetPrimaryFanart(iFanart); + CVideoDatabase db; + if (db.Open()) + { + db.UpdateFanart(*m_movieItem, m_movieItem->GetVideoContentType()); + db.Close(); + } + result = m_movieItem->GetVideoInfoTag()->m_fanart.GetImageURL(); + } + else if (StringUtils::EqualsNoCase(result, "fanart://None") || !CFileUtils::Exists(result)) + result.clear(); + + // set the fanart image + if (flip && !result.empty()) + result = CTextureUtils::GetWrappedImageURL(result, "", "flipped"); + CVideoDatabase db; + if (db.Open()) + { + db.SetArtForItem(m_movieItem->GetVideoInfoTag()->m_iDbId, m_movieItem->GetVideoInfoTag()->m_type, "fanart", result); + db.Close(); + } + + CUtil::DeleteVideoDatabaseDirectoryCache(); // to get them new thumbs to show + m_movieItem->SetArt("fanart", result); + m_hasUpdatedThumb = true; + + // Update our screen + Update(); +} + +void CGUIDialogVideoInfo::OnSetUserrating() const +{ + CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); + if (dialog) + { + dialog->SetHeading(CVariant{ 38023 }); + dialog->Add(g_localizeStrings.Get(38022)); + for (int i = 1; i <= 10; i++) + dialog->Add(StringUtils::Format("{}: {}", g_localizeStrings.Get(563), i)); + + dialog->SetSelected(m_movieItem->GetVideoInfoTag()->m_iUserRating); + + dialog->Open(); + + int iItem = dialog->GetSelectedItem(); + if (iItem < 0) + return; + + SetUserrating(iItem); + } +} + +void CGUIDialogVideoInfo::PlayTrailer() +{ + Close(true); + CGUIMessage msg(GUI_MSG_PLAY_TRAILER, 0, 0, 0, 0, m_movieItem); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); +} + +void CGUIDialogVideoInfo::SetLabel(int iControl, const std::string &strLabel) +{ + if (strLabel.empty()) + { + SET_CONTROL_LABEL(iControl, 416); // "Not available" + } + else + { + SET_CONTROL_LABEL(iControl, strLabel); + } +} + +std::string CGUIDialogVideoInfo::GetThumbnail() const +{ + return m_movieItem->GetArt("thumb"); +} + +namespace +{ +std::string GetItemPathForBrowserSource(const CFileItem& item) +{ + if (!item.HasVideoInfoTag()) + return ""; + + std::string itemDir = item.GetVideoInfoTag()->m_basePath; + //season + if (itemDir.empty()) + itemDir = item.GetVideoInfoTag()->GetPath(); + + CFileItem itemTmp(itemDir, false); + if (itemTmp.IsVideo()) + itemDir = URIUtils::GetParentPath(itemDir); + + return itemDir; +} + +void AddItemPathStringToFileBrowserSources(VECSOURCES& sources, + const std::string& itemDir, const std::string& label) +{ + if (!itemDir.empty() && CDirectory::Exists(itemDir)) + { + CMediaSource itemSource; + itemSource.strName = label; + itemSource.strPath = itemDir; + sources.push_back(itemSource); + } +} +} // namespace + +void CGUIDialogVideoInfo::AddItemPathToFileBrowserSources(VECSOURCES& sources, + const CFileItem& item) +{ + std::string itemDir = GetItemPathForBrowserSource(item); + AddItemPathStringToFileBrowserSources(sources, itemDir, g_localizeStrings.Get(36041)); +} + +int CGUIDialogVideoInfo::ManageVideoItem(const std::shared_ptr& item) +{ + if (item == nullptr || !item->IsVideoDb() || !item->HasVideoInfoTag() || item->GetVideoInfoTag()->m_iDbId < 0) + return -1; + + CVideoDatabase database; + if (!database.Open()) + return -1; + + const std::string &type = item->GetVideoInfoTag()->m_type; + int dbId = item->GetVideoInfoTag()->m_iDbId; + + CContextButtons buttons; + if (type == MediaTypeMovie || type == MediaTypeVideoCollection || + type == MediaTypeTvShow || type == MediaTypeEpisode || + (type == MediaTypeSeason && item->GetVideoInfoTag()->m_iSeason > 0) || // seasons without "all seasons" and "specials" + type == MediaTypeMusicVideo) + buttons.Add(CONTEXT_BUTTON_EDIT, 16105); + + if (type == MediaTypeMovie || type == MediaTypeTvShow) + buttons.Add(CONTEXT_BUTTON_EDIT_SORTTITLE, 16107); + + if (type == MediaTypeMovie) + { + // only show link/unlink if there are tvshows available + if (database.HasContent(VideoDbContentType::TVSHOWS)) + { + buttons.Add(CONTEXT_BUTTON_LINK_MOVIE, 20384); + if (database.IsLinkedToTvshow(dbId)) + buttons.Add(CONTEXT_BUTTON_UNLINK_MOVIE, 20385); + } + + // set or change movie set the movie belongs to + buttons.Add(CONTEXT_BUTTON_SET_MOVIESET, 20465); + } + + if (type == MediaTypeEpisode && + item->GetVideoInfoTag()->m_iBookmarkId > 0) + buttons.Add(CONTEXT_BUTTON_UNLINK_BOOKMARK, 20405); + + // movie sets + if (item->m_bIsFolder && type == MediaTypeVideoCollection) + { + buttons.Add(CONTEXT_BUTTON_SET_MOVIESET_ART, 13511); + buttons.Add(CONTEXT_BUTTON_MOVIESET_ADD_REMOVE_ITEMS, 20465); + } + + // seasons + if (item->m_bIsFolder && type == MediaTypeSeason) + buttons.Add(CONTEXT_BUTTON_SET_SEASON_ART, 13511); + + // tags + if (item->m_bIsFolder && type == "tag") + { + CVideoDbUrl videoUrl; + if (videoUrl.FromString(item->GetPath())) + { + const std::string &mediaType = videoUrl.GetItemType(); + + buttons.Add( + CONTEXT_BUTTON_TAGS_ADD_ITEMS, + StringUtils::Format(g_localizeStrings.Get(20460), GetLocalizedVideoType(mediaType))); + buttons.Add(CONTEXT_BUTTON_TAGS_REMOVE_ITEMS, StringUtils::Format(g_localizeStrings.Get(20461).c_str(), GetLocalizedVideoType(mediaType).c_str())); + } + } + + if (type != MediaTypeSeason) + buttons.Add(CONTEXT_BUTTON_DELETE, 646); + + //temporary workaround until the context menu ids are removed + const int addonItemOffset = 10000; + + auto addonItems = CServiceBroker::GetContextMenuManager().GetAddonItems(*item, CContextMenuManager::MANAGE); + for (size_t i = 0; i < addonItems.size(); ++i) + buttons.Add(addonItemOffset + i, addonItems[i]->GetLabel(*item)); + + bool result = false; + int button = CGUIDialogContextMenu::ShowAndGetChoice(buttons); + if (button >= 0) + { + switch (static_cast(button)) + { + case CONTEXT_BUTTON_EDIT: + result = UpdateVideoItemTitle(item); + break; + + case CONTEXT_BUTTON_EDIT_SORTTITLE: + result = UpdateVideoItemSortTitle(item); + break; + + case CONTEXT_BUTTON_LINK_MOVIE: + result = LinkMovieToTvShow(item, false, database); + break; + + case CONTEXT_BUTTON_UNLINK_MOVIE: + result = LinkMovieToTvShow(item, true, database); + break; + + case CONTEXT_BUTTON_SET_MOVIESET: + { + CFileItemPtr selectedSet; + if (GetSetForMovie(item.get(), selectedSet)) + result = SetMovieSet(item.get(), selectedSet.get()); + break; + } + + case CONTEXT_BUTTON_UNLINK_BOOKMARK: + database.DeleteBookMarkForEpisode(*item->GetVideoInfoTag()); + result = true; + break; + + case CONTEXT_BUTTON_DELETE: + result = DeleteVideoItem(item); + break; + + case CONTEXT_BUTTON_SET_MOVIESET_ART: + case CONTEXT_BUTTON_SET_SEASON_ART: + result = ManageVideoItemArtwork(item, type); + break; + + case CONTEXT_BUTTON_MOVIESET_ADD_REMOVE_ITEMS: + result = ManageMovieSets(item); + break; + + case CONTEXT_BUTTON_TAGS_ADD_ITEMS: + result = AddItemsToTag(item); + break; + + case CONTEXT_BUTTON_TAGS_REMOVE_ITEMS: + result = RemoveItemsFromTag(item); + break; + + default: + if (button >= addonItemOffset) + result = CONTEXTMENU::LoopFrom(*addonItems[button - addonItemOffset], item); + break; + } + } + + database.Close(); + + if (result) + return button; + + return -1; +} + +//Add change a title's name +bool CGUIDialogVideoInfo::UpdateVideoItemTitle(const std::shared_ptr& pItem) +{ + if (pItem == nullptr || !pItem->HasVideoInfoTag()) + return false; + + // dont allow update while scanning + if (CVideoLibraryQueue::GetInstance().IsScanningLibrary()) + { + HELPERS::ShowOKDialogText(CVariant{257}, CVariant{14057}); + return false; + } + + CVideoDatabase database; + if (!database.Open()) + return false; + + int iDbId = pItem->GetVideoInfoTag()->m_iDbId; + MediaType mediaType = pItem->GetVideoInfoTag()->m_type; + + CVideoInfoTag detail; + std::string title; + if (mediaType == MediaTypeMovie) + { + database.GetMovieInfo("", detail, iDbId, VideoDbDetailsNone); + title = detail.m_strTitle; + } + else if (mediaType == MediaTypeVideoCollection) + { + database.GetSetInfo(iDbId, detail); + title = detail.m_strTitle; + } + else if (mediaType == MediaTypeEpisode) + { + database.GetEpisodeInfo(pItem->GetPath(), detail, iDbId, VideoDbDetailsNone); + title = detail.m_strTitle; + } + else if (mediaType == MediaTypeSeason) + { + database.GetSeasonInfo(iDbId, detail); + title = detail.m_strSortTitle.empty() ? detail.m_strTitle : detail.m_strSortTitle; + } + else if (mediaType == MediaTypeTvShow) + { + database.GetTvShowInfo(pItem->GetVideoInfoTag()->m_strFileNameAndPath, detail, iDbId, 0, VideoDbDetailsNone); + title = detail.m_strTitle; + } + else if (mediaType == MediaTypeMusicVideo) + { + database.GetMusicVideoInfo(pItem->GetVideoInfoTag()->m_strFileNameAndPath, detail, iDbId, VideoDbDetailsNone); + title = detail.m_strTitle; + } + + // get the new title + if (!CGUIKeyboardFactory::ShowAndGetInput(title, CVariant{ g_localizeStrings.Get(16105) }, false)) + return false; + + if (mediaType == MediaTypeSeason) + { + detail.m_strSortTitle = title; + std::map artwork; + database.SetDetailsForSeason(detail, artwork, detail.m_iIdShow, detail.m_iDbId); + } + else + { + detail.m_strTitle = title; + VideoDbContentType iType = pItem->GetVideoContentType(); + database.UpdateMovieTitle(iDbId, detail.m_strTitle, iType); + } + + return true; +} + +bool CGUIDialogVideoInfo::CanDeleteVideoItem(const std::shared_ptr& item) +{ + if (item == nullptr || !item->HasVideoInfoTag()) + return false; + + if (item->GetVideoInfoTag()->m_type == "tag") + return true; + + CQueryParams params; + CVideoDatabaseDirectory::GetQueryParams(item->GetPath(), params); + + return params.GetMovieId() != -1 || + params.GetEpisodeId() != -1 || + params.GetMVideoId() != -1 || + params.GetSetId() != -1 || + (params.GetTvShowId() != -1 && params.GetSeason() <= -1 && + !CVideoDatabaseDirectory::IsAllItem(item->GetPath())); +} + +bool CGUIDialogVideoInfo::DeleteVideoItemFromDatabase(const std::shared_ptr& item, + bool unavailable /* = false */) +{ + if (item == nullptr || !item->HasVideoInfoTag() || + !CanDeleteVideoItem(item)) + return false; + + // dont allow update while scanning + if (CVideoLibraryQueue::GetInstance().IsScanningLibrary()) + { + HELPERS::ShowOKDialogText(CVariant{257}, CVariant{14057}); + return false; + } + + CGUIDialogYesNo* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_YES_NO); + if (pDialog == nullptr) + return false; + + int heading = -1; + VideoDbContentType type = item->GetVideoContentType(); + const std::string& subtype = item->GetVideoInfoTag()->m_type; + if (subtype != "tag") + { + switch (type) + { + case VideoDbContentType::MOVIES: + heading = 432; + break; + case VideoDbContentType::EPISODES: + heading = 20362; + break; + case VideoDbContentType::TVSHOWS: + heading = 20363; + break; + case VideoDbContentType::MUSICVIDEOS: + heading = 20392; + break; + case VideoDbContentType::MOVIE_SETS: + heading = 646; + break; + default: + return false; + } + } + else + { + heading = 10058; + } + + pDialog->SetHeading(CVariant{heading}); + + if (unavailable) + { + pDialog->SetLine(0, CVariant{g_localizeStrings.Get(662)}); + pDialog->SetLine(1, CVariant{g_localizeStrings.Get(663)}); + } + else + { + pDialog->SetLine(0, + CVariant{StringUtils::Format(g_localizeStrings.Get(433), item->GetLabel())}); + pDialog->SetLine(1, CVariant{""}); + } + pDialog->SetLine(2, CVariant{""}); + pDialog->Open(); + + if (!pDialog->IsConfirmed()) + return false; + + CVideoDatabase database; + database.Open(); + + if (item->GetVideoInfoTag()->m_iDbId < 0) + return false; + + if (subtype == "tag") + { + database.DeleteTag(item->GetVideoInfoTag()->m_iDbId, type); + return true; + } + + switch (type) + { + case VideoDbContentType::MOVIES: + database.DeleteMovie(item->GetVideoInfoTag()->m_iDbId); + break; + case VideoDbContentType::EPISODES: + database.DeleteEpisode(item->GetVideoInfoTag()->m_iDbId); + break; + case VideoDbContentType::TVSHOWS: + database.DeleteTvShow(item->GetVideoInfoTag()->m_iDbId); + break; + case VideoDbContentType::MUSICVIDEOS: + database.DeleteMusicVideo(item->GetVideoInfoTag()->m_iDbId); + break; + case VideoDbContentType::MOVIE_SETS: + database.DeleteSet(item->GetVideoInfoTag()->m_iDbId); + break; + default: + return false; + } + return true; +} + +bool CGUIDialogVideoInfo::DeleteVideoItem(const std::shared_ptr& item, + bool unavailable /* = false */) +{ + if (item == nullptr) + return false; + + // delete the video item from the database + if (!DeleteVideoItemFromDatabase(item, unavailable)) + return false; + + const std::shared_ptr profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + // check if the user is allowed to delete the actual file as well + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_ALLOWFILEDELETION) && + (profileManager->GetCurrentProfile().getLockMode() == LOCK_MODE_EVERYONE || + !profileManager->GetCurrentProfile().filesLocked() || + g_passwordManager.IsMasterLockUnlocked(true))) + { + std::string strDeletePath = item->GetVideoInfoTag()->GetPath(); + + if (StringUtils::EqualsNoCase(URIUtils::GetFileName(strDeletePath), "VIDEO_TS.IFO")) + { + strDeletePath = URIUtils::GetDirectory(strDeletePath); + if (StringUtils::EndsWithNoCase(strDeletePath, "video_ts/")) + { + URIUtils::RemoveSlashAtEnd(strDeletePath); + strDeletePath = URIUtils::GetDirectory(strDeletePath); + } + } + if (URIUtils::HasSlashAtEnd(strDeletePath)) + item->m_bIsFolder = true; + + // check if the file/directory can be deleted + if (CUtil::SupportsWriteFileOperations(strDeletePath)) + { + item->SetPath(strDeletePath); + + // HACK: stacked files need to be treated as folders in order to be deleted + if (item->IsStack()) + item->m_bIsFolder = true; + + CGUIComponent *gui = CServiceBroker::GetGUI(); + if (gui && gui->ConfirmDelete(item->GetPath())) + CFileUtils::DeleteItem(item); + } + } + + CUtil::DeleteVideoDatabaseDirectoryCache(); + + return true; +} + +bool CGUIDialogVideoInfo::ManageMovieSets(const std::shared_ptr& item) +{ + if (item == nullptr) + return false; + + CFileItemList originalItems; + CFileItemList selectedItems; + + if (!GetMoviesForSet(item.get(), originalItems, selectedItems) || + selectedItems.Size() == 0) // need at least one item selected + return false; + + VECFILEITEMS original = originalItems.GetList(); + std::sort(original.begin(), original.end(), compFileItemsByDbId); + VECFILEITEMS selected = selectedItems.GetList(); + std::sort(selected.begin(), selected.end(), compFileItemsByDbId); + + bool refreshNeeded = false; + // update the "added" items + VECFILEITEMS addedItems; + set_difference(selected.begin(),selected.end(), original.begin(),original.end(), std::back_inserter(addedItems), compFileItemsByDbId); + for (VECFILEITEMS::const_iterator it = addedItems.begin(); it != addedItems.end(); ++it) + { + if (SetMovieSet(it->get(), item.get())) + refreshNeeded = true; + } + + // update the "deleted" items + CFileItemPtr clearItem(new CFileItem()); + clearItem->GetVideoInfoTag()->m_iDbId = -1; // -1 will be used to clear set + VECFILEITEMS deletedItems; + set_difference(original.begin(),original.end(), selected.begin(),selected.end(), std::back_inserter(deletedItems), compFileItemsByDbId); + for (VECFILEITEMS::iterator it = deletedItems.begin(); it != deletedItems.end(); ++it) + { + if (SetMovieSet(it->get(), clearItem.get())) + refreshNeeded = true; + } + + return refreshNeeded; +} + +bool CGUIDialogVideoInfo::GetMoviesForSet(const CFileItem *setItem, CFileItemList &originalMovies, CFileItemList &selectedMovies) +{ + if (setItem == nullptr || !setItem->HasVideoInfoTag()) + return false; + + CVideoDatabase videodb; + if (!videodb.Open()) + return false; + + std::string baseDir = + StringUtils::Format("videodb://movies/sets/{}", setItem->GetVideoInfoTag()->m_iDbId); + + if (!CDirectory::GetDirectory(baseDir, originalMovies, "", DIR_FLAG_DEFAULTS) || + originalMovies.Size() <= 0) // keep a copy of the original members of the set + return false; + + CFileItemList listItems; + if (!videodb.GetSortedVideos(MediaTypeMovie, "videodb://movies", SortDescription(), listItems) || listItems.Size() <= 0) + return false; + + CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); + if (dialog == nullptr) + return false; + + listItems.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); + + dialog->Reset(); + dialog->SetMultiSelection(true); + dialog->SetHeading(CVariant{g_localizeStrings.Get(20457)}); + dialog->SetItems(listItems); + std::vector selectedIndices; + for (int i = 0; i < originalMovies.Size(); i++) + { + for (int listIndex = 0; listIndex < listItems.Size(); listIndex++) + { + if (listItems.Get(listIndex)->GetVideoInfoTag()->m_iDbId == originalMovies[i]->GetVideoInfoTag()->m_iDbId) + { + selectedIndices.push_back(listIndex); + break; + } + } + } + dialog->SetSelected(selectedIndices); + dialog->EnableButton(true, 186); + dialog->Open(); + + if (dialog->IsConfirmed()) + { + for (int i : dialog->GetSelectedItems()) + selectedMovies.Add(listItems.Get(i)); + return (selectedMovies.Size() > 0); + } + else + return false; +} + +bool CGUIDialogVideoInfo::GetSetForMovie(const CFileItem* movieItem, + std::shared_ptr& selectedSet) +{ + if (movieItem == nullptr || !movieItem->HasVideoInfoTag()) + return false; + + CVideoDatabase videodb; + if (!videodb.Open()) + return false; + + CFileItemList listItems; + + // " ignoreSingleMovieSets=false " as an option in the url is needed here + // to override the gui-setting "Include sets containing a single movie" + // and retrieve all moviesets + + std::string baseDir = "videodb://movies/sets/?ignoreSingleMovieSets=false"; + + if (!CDirectory::GetDirectory(baseDir, listItems, "", DIR_FLAG_DEFAULTS)) + return false; + listItems.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); + + int currentSetId = 0; + std::string currentSetLabel; + + if (movieItem->GetVideoInfoTag()->m_set.id > currentSetId) + { + currentSetId = movieItem->GetVideoInfoTag()->m_set.id; + currentSetLabel = videodb.GetSetById(currentSetId); + } + + if (currentSetId > 0) + { + // remove duplicate entry + for (int listIndex = 0; listIndex < listItems.Size(); listIndex++) + { + if (listItems.Get(listIndex)->GetVideoInfoTag()->m_iDbId == currentSetId) + { + listItems.Remove(listIndex); + break; + } + } + // add clear item + std::string strClear = StringUtils::Format(g_localizeStrings.Get(20467), currentSetLabel); + CFileItemPtr clearItem(new CFileItem(strClear)); + clearItem->GetVideoInfoTag()->m_iDbId = -1; // -1 will be used to clear set + listItems.AddFront(clearItem, 0); + // add keep current set item + std::string strKeep = StringUtils::Format(g_localizeStrings.Get(20469), currentSetLabel); + CFileItemPtr keepItem(new CFileItem(strKeep)); + keepItem->GetVideoInfoTag()->m_iDbId = currentSetId; + listItems.AddFront(keepItem, 1); + } + + CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); + if (dialog == nullptr) + return false; + + dialog->Reset(); + dialog->SetHeading(CVariant{g_localizeStrings.Get(20466)}); + dialog->SetItems(listItems); + if (currentSetId >= 0) + { + for (int listIndex = 0; listIndex < listItems.Size(); listIndex++) + { + if (listItems.Get(listIndex)->GetVideoInfoTag()->m_iDbId == currentSetId) + { + dialog->SetSelected(listIndex); + break; + } + } + } + dialog->EnableButton(true, 20468); // new set via button + dialog->Open(); + + if (dialog->IsButtonPressed()) + { // creating new set + std::string newSetTitle; + if (!CGUIKeyboardFactory::ShowAndGetInput(newSetTitle, CVariant{g_localizeStrings.Get(20468)}, false)) + return false; + int idSet = videodb.AddSet(newSetTitle); + std::map movieArt, setArt; + if (!videodb.GetArtForItem(idSet, MediaTypeVideoCollection, setArt)) + { + videodb.GetArtForItem(movieItem->GetVideoInfoTag()->m_iDbId, MediaTypeMovie, movieArt); + videodb.SetArtForItem(idSet, MediaTypeVideoCollection, movieArt); + } + CFileItemPtr newSet(new CFileItem(newSetTitle)); + newSet->GetVideoInfoTag()->m_iDbId = idSet; + selectedSet = newSet; + return true; + } + else if (dialog->IsConfirmed()) + { + selectedSet = dialog->GetSelectedFileItem(); + return (selectedSet != nullptr); + } + else + return false; +} + +bool CGUIDialogVideoInfo::SetMovieSet(const CFileItem *movieItem, const CFileItem *selectedSet) +{ + if (movieItem == nullptr || !movieItem->HasVideoInfoTag() || + selectedSet == nullptr || !selectedSet->HasVideoInfoTag()) + return false; + + CVideoDatabase videodb; + if (!videodb.Open()) + return false; + + videodb.SetMovieSet(movieItem->GetVideoInfoTag()->m_iDbId, selectedSet->GetVideoInfoTag()->m_iDbId); + return true; +} + +bool CGUIDialogVideoInfo::GetItemsForTag(const std::string &strHeading, const std::string &type, CFileItemList &items, int idTag /* = -1 */, bool showAll /* = true */) +{ + CVideoDatabase videodb; + if (!videodb.Open()) + return false; + + MediaType mediaType = MediaTypeNone; + std::string baseDir = "videodb://"; + std::string idColumn; + if (type.compare(MediaTypeMovie) == 0) + { + mediaType = MediaTypeMovie; + baseDir += "movies"; + idColumn = "idMovie"; + } + else if (type.compare(MediaTypeTvShow) == 0) + { + mediaType = MediaTypeTvShow; + baseDir += "tvshows"; + idColumn = "idShow"; + } + else if (type.compare(MediaTypeMusicVideo) == 0) + { + mediaType = MediaTypeMusicVideo; + baseDir += "musicvideos"; + idColumn = "idMVideo"; + } + + baseDir += "/titles/"; + CVideoDbUrl videoUrl; + if (!videoUrl.FromString(baseDir)) + return false; + + CVideoDatabase::Filter filter; + if (idTag > 0) + { + if (!showAll) + videoUrl.AddOption("tagid", idTag); + else + filter.where = videodb.PrepareSQL("%s_view.%s NOT IN (SELECT tag_link.media_id FROM tag_link WHERE tag_link.tag_id = %d AND tag_link.media_type = '%s')", type.c_str(), idColumn.c_str(), idTag, type.c_str()); + } + + CFileItemList listItems; + if (!videodb.GetSortedVideos(mediaType, videoUrl.ToString(), SortDescription(), listItems, filter) || listItems.Size() <= 0) + return false; + + CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); + if (dialog == nullptr) + return false; + + listItems.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); + + dialog->Reset(); + dialog->SetMultiSelection(true); + dialog->SetHeading(CVariant{strHeading}); + dialog->SetItems(listItems); + dialog->EnableButton(true, 186); + dialog->Open(); + + for (int i : dialog->GetSelectedItems()) + items.Add(listItems.Get(i)); + return items.Size() > 0; +} + +bool CGUIDialogVideoInfo::AddItemsToTag(const std::shared_ptr& tagItem) +{ + if (tagItem == nullptr || !tagItem->HasVideoInfoTag()) + return false; + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString(tagItem->GetPath())) + return false; + + CVideoDatabase videodb; + if (!videodb.Open()) + return true; + + std::string mediaType = videoUrl.GetItemType(); + mediaType = mediaType.substr(0, mediaType.length() - 1); + + CFileItemList items; + std::string localizedType = GetLocalizedVideoType(mediaType); + std::string strLabel = StringUtils::Format(g_localizeStrings.Get(20464), localizedType); + if (!GetItemsForTag(strLabel, mediaType, items, tagItem->GetVideoInfoTag()->m_iDbId)) + return true; + + for (int index = 0; index < items.Size(); index++) + { + if (!items[index]->HasVideoInfoTag() || items[index]->GetVideoInfoTag()->m_iDbId <= 0) + continue; + + videodb.AddTagToItem(items[index]->GetVideoInfoTag()->m_iDbId, tagItem->GetVideoInfoTag()->m_iDbId, mediaType); + } + + return true; +} + +bool CGUIDialogVideoInfo::RemoveItemsFromTag(const std::shared_ptr& tagItem) +{ + if (tagItem == nullptr || !tagItem->HasVideoInfoTag()) + return false; + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString(tagItem->GetPath())) + return false; + + CVideoDatabase videodb; + if (!videodb.Open()) + return true; + + std::string mediaType = videoUrl.GetItemType(); + mediaType = mediaType.substr(0, mediaType.length() - 1); + + CFileItemList items; + std::string localizedType = GetLocalizedVideoType(mediaType); + std::string strLabel = StringUtils::Format(g_localizeStrings.Get(20464), localizedType); + if (!GetItemsForTag(strLabel, mediaType, items, tagItem->GetVideoInfoTag()->m_iDbId, false)) + return true; + + for (int index = 0; index < items.Size(); index++) + { + if (!items[index]->HasVideoInfoTag() || items[index]->GetVideoInfoTag()->m_iDbId <= 0) + continue; + + videodb.RemoveTagFromItem(items[index]->GetVideoInfoTag()->m_iDbId, tagItem->GetVideoInfoTag()->m_iDbId, mediaType); + } + + return true; +} + +namespace +{ +std::string FindLocalMovieSetArtworkFile(const CFileItemPtr& item, const std::string& artType) +{ + std::string infoFolder = VIDEO::CVideoInfoScanner::GetMovieSetInfoFolder(item->GetLabel()); + if (infoFolder.empty()) + return ""; + + CFileItemList availableArtFiles; + CDirectory::GetDirectory(infoFolder, availableArtFiles, + CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), + DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO); + for (const auto& artFile : availableArtFiles) + { + std::string candidate = URIUtils::GetFileName(artFile->GetPath()); + URIUtils::RemoveExtension(candidate); + if (StringUtils::EqualsNoCase(candidate, artType)) + return artFile->GetPath(); + } + return ""; +} +} // namespace + +bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr& item, + const MediaType& type) +{ + if (item == nullptr || !item->HasVideoInfoTag() || type.empty()) + return false; + + CVideoDatabase videodb; + if (!videodb.Open()) + return true; + + // Grab the thumbnails from the web + CFileItemList items; + CFileItemPtr noneitem(new CFileItem("thumb://None", false)); + std::string currentThumb; + int idArtist = -1; + std::string artistPath; + std::string artistOldPath; + std::string artType = "thumb"; + if (type == MediaTypeArtist) + { + CMusicDatabase musicdb; + if (musicdb.Open()) + { + idArtist = musicdb.GetArtistByName(item->GetLabel()); // Fails when name not unique + if (idArtist >= 0 ) + { + // Get artist paths - possible locations for thumb - while music db open + musicdb.GetOldArtistPath(idArtist, artistOldPath); // Old artist path, local to music files + CArtist artist; + musicdb.GetArtist(idArtist, artist); // Need name and mbid for artist folder name + musicdb.GetArtistPath(artist, artistPath); // Artist path in artist info folder + + currentThumb = musicdb.GetArtForItem(idArtist, MediaTypeArtist, "thumb"); + if (currentThumb.empty()) + currentThumb = videodb.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType); + } + } + } + else if (type == "actor") + currentThumb = videodb.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType); + else + { // SEASON, SET + artType = ChooseArtType(*item); + if (artType.empty()) + return false; + + if (artType == "fanart" && type != MediaTypeVideoCollection) + return OnGetFanart(item); + + if (item->HasArt(artType)) + currentThumb = item->GetArt(artType); + else if ((artType == "poster" || artType == "banner") && item->HasArt("thumb")) + currentThumb = item->GetArt("thumb"); + } + + if (!currentThumb.empty()) + { + CFileItemPtr item(new CFileItem("thumb://Current", false)); + item->SetArt("thumb", currentThumb); + item->SetLabel(g_localizeStrings.Get(13512)); + items.Add(item); + } + noneitem->SetArt("icon", "DefaultFolder.png"); + noneitem->SetLabel(g_localizeStrings.Get(13515)); + + bool local = false; + std::vector thumbs; + if (type != MediaTypeArtist) + { + CVideoInfoTag tag; + if (type == MediaTypeSeason) + { + videodb.GetTvShowInfo("", tag, item->GetVideoInfoTag()->m_iIdShow); + tag.m_strPictureURL.Parse(); + tag.m_strPictureURL.GetThumbUrls(thumbs, artType, item->GetVideoInfoTag()->m_iSeason); + } + else if (type == MediaTypeVideoCollection) + { + CFileItemList items; + std::string baseDir = + StringUtils::Format("videodb://movies/sets/{}", item->GetVideoInfoTag()->m_iDbId); + if (videodb.GetMoviesNav(baseDir, items)) + { + for (int i=0; i < items.Size(); i++) + { + CVideoInfoTag* pTag = items[i]->GetVideoInfoTag(); + pTag->m_strPictureURL.Parse(); + pTag->m_strPictureURL.GetThumbUrls(thumbs, "set." + artType, -1, true); + } + } + } + else + { + tag = *item->GetVideoInfoTag(); + tag.m_strPictureURL.Parse(); + tag.m_strPictureURL.GetThumbUrls(thumbs, artType); + } + + for (size_t i = 0; i < thumbs.size(); i++) + { + CFileItemPtr item(new CFileItem(StringUtils::Format("thumb://Remote{0}", i), false)); + item->SetArt("thumb", thumbs[i]); + item->SetArt("icon", "DefaultPicture.png"); + item->SetLabel(g_localizeStrings.Get(13513)); + items.Add(item); + + //! @todo Do we need to clear the cached image? + // CServiceBroker::GetTextureCache()->ClearCachedImage(thumbs[i]); + } + + if (type == "actor") + { + std::string picturePath; + std::string strThumb = URIUtils::AddFileToFolder(picturePath, "folder.jpg"); + if (CFileUtils::Exists(strThumb)) + { + CFileItemPtr pItem(new CFileItem(strThumb,false)); + pItem->SetLabel(g_localizeStrings.Get(13514)); + pItem->SetArt("thumb", strThumb); + items.Add(pItem); + local = true; + } + else + noneitem->SetArt("icon", "DefaultActor.png"); + } + + if (type == MediaTypeVideoCollection) + { + std::string localFile = FindLocalMovieSetArtworkFile(item, artType); + if (!localFile.empty()) + { + CFileItemPtr pItem(new CFileItem(localFile, false)); + pItem->SetLabel(g_localizeStrings.Get(13514)); + pItem->SetArt("thumb", localFile); + items.Add(pItem); + local = true; + } + else + noneitem->SetArt("icon", "DefaultVideo.png"); + } + } + else + { + std::string strThumb; + bool existsThumb = false; + // First look for artist thumb in the primary location + if (!artistPath.empty()) + { + strThumb = URIUtils::AddFileToFolder(artistPath, "folder.jpg"); + existsThumb = CFileUtils::Exists(strThumb); + } + // If not there fall back local to music files (historic location for those album artists with a unique folder) + if (!existsThumb && !artistOldPath.empty()) + { + strThumb = URIUtils::AddFileToFolder(artistOldPath, "folder.jpg"); + existsThumb = CFileUtils::Exists(strThumb); + } + + if (existsThumb) + { + CFileItemPtr pItem(new CFileItem(strThumb, false)); + pItem->SetLabel(g_localizeStrings.Get(13514)); + pItem->SetArt("thumb", strThumb); + items.Add(pItem); + local = true; + } + else + noneitem->SetArt("icon", "DefaultArtist.png"); + } + + if (!local) + items.Add(noneitem); + + std::string result; + VECSOURCES sources=*CMediaSourceSettings::GetInstance().GetSources("video"); + CServiceBroker::GetMediaManager().GetLocalDrives(sources); + if (type == MediaTypeVideoCollection) + { + AddItemPathStringToFileBrowserSources(sources, + VIDEO::CVideoInfoScanner::GetMovieSetInfoFolder(item->GetLabel()), + g_localizeStrings.Get(36041)); + AddItemPathStringToFileBrowserSources(sources, + CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( + CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER), + "* " + g_localizeStrings.Get(20226)); + } + else + AddItemPathToFileBrowserSources(sources, *item); + if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(13511), result)) + return false; // user cancelled + + if (result == "thumb://Current") + result = currentThumb; // user chose the one they have + + // delete the thumbnail if that's what the user wants, else overwrite with the + // new thumbnail + if (result == "thumb://None") + result.clear(); + else if (StringUtils::StartsWith(result, "thumb://Remote")) + { + int number = atoi(StringUtils::Mid(result, 14).c_str()); + result = thumbs[number]; + } + + // write the selected artwork to the database + if (type == MediaTypeVideoCollection || + type == "actor" || + type == MediaTypeSeason || + (type == MediaTypeArtist && idArtist < 0)) + videodb.SetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType, result); + else + { + CMusicDatabase musicdb; + if (musicdb.Open()) + musicdb.SetArtForItem(idArtist, MediaTypeArtist, artType, result); + } + + item->SetArt(artType, result); + + CUtil::DeleteVideoDatabaseDirectoryCache(); + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REFRESH_THUMBS); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + + return true; +} + +std::string CGUIDialogVideoInfo::GetLocalizedVideoType(const std::string &strType) +{ + if (CMediaTypes::IsMediaType(strType, MediaTypeMovie)) + return g_localizeStrings.Get(20342); + else if (CMediaTypes::IsMediaType(strType, MediaTypeTvShow)) + return g_localizeStrings.Get(20343); + else if (CMediaTypes::IsMediaType(strType, MediaTypeEpisode)) + return g_localizeStrings.Get(20359); + else if (CMediaTypes::IsMediaType(strType, MediaTypeMusicVideo)) + return g_localizeStrings.Get(20391); + + return ""; +} + +bool CGUIDialogVideoInfo::UpdateVideoItemSortTitle(const std::shared_ptr& pItem) +{ + // dont allow update while scanning + if (CVideoLibraryQueue::GetInstance().IsScanningLibrary()) + { + HELPERS::ShowOKDialogText(CVariant{257}, CVariant{14057}); + return false; + } + + CVideoDatabase database; + if (!database.Open()) + return false; + + int iDbId = pItem->GetVideoInfoTag()->m_iDbId; + CVideoInfoTag detail; + VideoDbContentType iType = pItem->GetVideoContentType(); + if (iType == VideoDbContentType::MOVIES) + database.GetMovieInfo("", detail, iDbId, VideoDbDetailsNone); + else if (iType == VideoDbContentType::TVSHOWS) + database.GetTvShowInfo(pItem->GetVideoInfoTag()->m_strFileNameAndPath, detail, iDbId, 0, VideoDbDetailsNone); + + std::string currentTitle; + if (detail.m_strSortTitle.empty()) + currentTitle = detail.m_strTitle; + else + currentTitle = detail.m_strSortTitle; + + // get the new sort title + if (!CGUIKeyboardFactory::ShowAndGetInput(currentTitle, CVariant{g_localizeStrings.Get(16107)}, false)) + return false; + + return database.UpdateVideoSortTitle(iDbId, currentTitle, iType); +} + +bool CGUIDialogVideoInfo::LinkMovieToTvShow(const std::shared_ptr& item, + bool bRemove, + CVideoDatabase& database) +{ + int dbId = item->GetVideoInfoTag()->m_iDbId; + + CFileItemList list; + if (bRemove) + { + std::vector ids; + if (!database.GetLinksToTvShow(dbId, ids)) + return false; + + for (unsigned int i = 0; i < ids.size(); ++i) + { + CVideoInfoTag tag; + database.GetTvShowInfo("", tag, ids[i], 0 , VideoDbDetailsNone); + CFileItemPtr show(new CFileItem(tag)); + list.Add(show); + } + } + else + { + database.GetTvShowsNav("videodb://tvshows/titles", list); + + // remove already linked shows + std::vector ids; + if (!database.GetLinksToTvShow(dbId, ids)) + return false; + + for (int i = 0; i < list.Size(); ) + { + size_t j; + for (j = 0; j < ids.size(); ++j) + { + if (list[i]->GetVideoInfoTag()->m_iDbId == ids[j]) + break; + } + if (j == ids.size()) + i++; + else + list.Remove(i); + } + } + + int iSelectedLabel = 0; + if (list.Size() > 1 || (!bRemove && !list.IsEmpty())) + { + list.Sort(SortByLabel, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); + CGUIDialogSelect* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); + if (pDialog) + { + pDialog->Reset(); + pDialog->SetItems(list); + pDialog->SetHeading(CVariant{20356}); + pDialog->Open(); + iSelectedLabel = pDialog->GetSelectedItem(); + } + } + + if (iSelectedLabel > -1 && iSelectedLabel < list.Size()) + return database.LinkMovieToTvshow(dbId, list[iSelectedLabel]->GetVideoInfoTag()->m_iDbId, bRemove); + + return false; +} + +bool CGUIDialogVideoInfo::OnGetFanart(const std::shared_ptr& videoItem) +{ + if (videoItem == nullptr || !videoItem->HasVideoInfoTag()) + return false; + + // update the db + CVideoDatabase videodb; + if (!videodb.Open()) + return false; + + CVideoThumbLoader loader; + CFileItem item(*videoItem); + loader.LoadItem(&item); + + CFileItemList items; + if (item.HasArt("fanart")) + { + CFileItemPtr itemCurrent(new CFileItem("fanart://Current", false)); + itemCurrent->SetArt("thumb", item.GetArt("fanart")); + itemCurrent->SetLabel(g_localizeStrings.Get(20440)); + items.Add(itemCurrent); + } + + // add the none option + { + CFileItemPtr itemNone(new CFileItem("fanart://None", false)); + itemNone->SetArt("icon", "DefaultVideo.png"); + itemNone->SetLabel(g_localizeStrings.Get(20439)); + items.Add(itemNone); + } + + std::string result; + VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("video")); + CServiceBroker::GetMediaManager().GetLocalDrives(sources); + AddItemPathToFileBrowserSources(sources, item); + bool flip = false; + if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(20437), result, &flip, 20445) || + StringUtils::EqualsNoCase(result, "fanart://Current")) + return false; + + else if (StringUtils::EqualsNoCase(result, "fanart://None") || !CFileUtils::Exists(result)) + result.clear(); + if (!result.empty() && flip) + result = CTextureUtils::GetWrappedImageURL(result, "", "flipped"); + + videodb.SetArtForItem(item.GetVideoInfoTag()->m_iDbId, item.GetVideoInfoTag()->m_type, "fanart", result); + + // clear view cache and reload images + CUtil::DeleteVideoDatabaseDirectoryCache(); + + return true; +} + +void CGUIDialogVideoInfo::ShowFor(const CFileItem& item) +{ + auto window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_VIDEO_NAV); + if (window) + { + ADDON::ScraperPtr info; + window->OnItemInfo(item, info); + } +} -- cgit v1.2.3