diff options
Diffstat (limited to 'xbmc/video/dialogs')
21 files changed, 6308 insertions, 0 deletions
diff --git a/xbmc/video/dialogs/CMakeLists.txt b/xbmc/video/dialogs/CMakeLists.txt new file mode 100644 index 0000000..b2ec11f --- /dev/null +++ b/xbmc/video/dialogs/CMakeLists.txt @@ -0,0 +1,26 @@ +set(SOURCES GUIDialogAudioSettings.cpp + GUIDialogFullScreenInfo.cpp + GUIDialogSubtitles.cpp + GUIDialogSubtitleSettings.cpp + GUIDialogTeletext.cpp + GUIDialogVideoBookmarks.cpp + GUIDialogVideoInfo.cpp + GUIDialogVideoOSD.cpp + GUIDialogVideoSettings.cpp) + +set(HEADERS GUIDialogAudioSettings.h + GUIDialogFullScreenInfo.h + GUIDialogSubtitles.h + GUIDialogSubtitleSettings.h + GUIDialogTeletext.h + GUIDialogVideoBookmarks.h + GUIDialogVideoInfo.h + GUIDialogVideoOSD.h + GUIDialogVideoSettings.h) + +if(OPENGL_FOUND OR CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) + list(APPEND SOURCES GUIDialogCMSSettings.cpp) + list(APPEND HEADERS GUIDialogCMSSettings.h) +endif() + +core_add_library(video_dialogs) diff --git a/xbmc/video/dialogs/GUIDialogAudioSettings.cpp b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp new file mode 100644 index 0000000..83b888b --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp @@ -0,0 +1,437 @@ +/* + * 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 "GUIDialogAudioSettings.h" + +#include "GUIPassword.h" +#include "ServiceBroker.h" +#include "addons/Skin.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "application/ApplicationVolumeHandling.h" +#include "cores/AudioEngine/Utils/AEUtil.h" +#include "cores/IPlayer.h" +#include "dialogs/GUIDialogYesNo.h" +#include "guilib/GUIMessage.h" +#include "guilib/LocalizeStrings.h" +#include "profiles/ProfileManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingsManager.h" +#include "utils/LangCodeExpander.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" + +#include <string> +#include <vector> + +#define SETTING_AUDIO_VOLUME "audio.volume" +#define SETTING_AUDIO_VOLUME_AMPLIFICATION "audio.volumeamplification" +#define SETTING_AUDIO_CENTERMIXLEVEL "audio.centermixlevel" +#define SETTING_AUDIO_DELAY "audio.delay" +#define SETTING_AUDIO_STREAM "audio.stream" +#define SETTING_AUDIO_PASSTHROUGH "audio.digitalanalog" +#define SETTING_AUDIO_MAKE_DEFAULT "audio.makedefault" + +CGUIDialogAudioSettings::CGUIDialogAudioSettings() + : CGUIDialogSettingsManualBase(WINDOW_DIALOG_AUDIO_OSD_SETTINGS, "DialogSettings.xml") +{ } + +CGUIDialogAudioSettings::~CGUIDialogAudioSettings() = default; + +void CGUIDialogAudioSettings::FrameMove() +{ + // update the volume setting if necessary + const auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + float newVolume = appVolume->GetVolumeRatio(); + if (newVolume != m_volume) + GetSettingsManager()->SetNumber(SETTING_AUDIO_VOLUME, static_cast<double>(newVolume)); + + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + const CVideoSettings videoSettings = appPlayer->GetVideoSettings(); + + // these settings can change on the fly + //! @todo (needs special handling): m_settingsManager->SetInt(SETTING_AUDIO_STREAM, g_application.GetAppPlayer().GetAudioStream()); + GetSettingsManager()->SetNumber(SETTING_AUDIO_DELAY, + static_cast<double>(videoSettings.m_AudioDelay)); + GetSettingsManager()->SetBool(SETTING_AUDIO_PASSTHROUGH, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH)); + } + + CGUIDialogSettingsManualBase::FrameMove(); +} + +std::string CGUIDialogAudioSettings::FormatDelay(float value, float interval) +{ + if (fabs(value) < 0.5f * interval) + return StringUtils::Format(g_localizeStrings.Get(22003), 0.0); + if (value < 0) + return StringUtils::Format(g_localizeStrings.Get(22004), fabs(value)); + + return StringUtils::Format(g_localizeStrings.Get(22005), value); +} + +std::string CGUIDialogAudioSettings::FormatDecibel(float value) +{ + return StringUtils::Format(g_localizeStrings.Get(14054), value); +} + +std::string CGUIDialogAudioSettings::FormatPercentAsDecibel(float value) +{ + return StringUtils::Format(g_localizeStrings.Get(14054), CAEUtil::PercentToGain(value)); +} + +void CGUIDialogAudioSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + const std::string &settingId = setting->GetId(); + if (settingId == SETTING_AUDIO_VOLUME) + { + m_volume = static_cast<float>(std::static_pointer_cast<const CSettingNumber>(setting)->GetValue()); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + appVolume->SetVolume(m_volume, false); // false - value is not in percent + } + else if (settingId == SETTING_AUDIO_VOLUME_AMPLIFICATION) + { + float value = static_cast<float>(std::static_pointer_cast<const CSettingNumber>(setting)->GetValue()); + appPlayer->SetDynamicRangeCompression((long)(value * 100)); + } + else if (settingId == SETTING_AUDIO_CENTERMIXLEVEL) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_CenterMixLevel = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_AUDIO_DELAY) + { + float value = static_cast<float>(std::static_pointer_cast<const CSettingNumber>(setting)->GetValue()); + appPlayer->SetAVDelay(value); + } + else if (settingId == SETTING_AUDIO_STREAM) + { + m_audioStream = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + // only change the audio stream if a different one has been asked for + if (appPlayer->GetAudioStream() != m_audioStream) + { + appPlayer->SetAudioStream(m_audioStream); // Set the audio stream to the one selected + } + } + else if (settingId == SETTING_AUDIO_PASSTHROUGH) + { + m_passthrough = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + CServiceBroker::GetSettingsComponent()->GetSettings()->SetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH, m_passthrough); + } +} + +void CGUIDialogAudioSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingAction(setting); + + const std::string &settingId = setting->GetId(); + if (settingId == SETTING_AUDIO_MAKE_DEFAULT) + Save(); +} + +bool CGUIDialogAudioSettings::Save() +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + if (!g_passwordManager.CheckSettingLevelLock(SettingLevel::Expert) && + profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE) + return true; + + // prompt user if they are sure + if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{12376}, CVariant{12377})) + return true; + + // reset the settings + CVideoDatabase db; + if (!db.Open()) + return true; + + db.EraseAllVideoSettings(); + db.Close(); + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + CMediaSettings::GetInstance().GetDefaultVideoSettings() = appPlayer->GetVideoSettings(); + CMediaSettings::GetInstance().GetDefaultVideoSettings().m_AudioStream = -1; + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + return true; +} + +void CGUIDialogAudioSettings::SetupView() +{ + CGUIDialogSettingsManualBase::SetupView(); + + SetHeading(13396); + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_OKAY_BUTTON); + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_CUSTOM_BUTTON); + SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 15067); +} + +void CGUIDialogAudioSettings::InitializeSettings() +{ + CGUIDialogSettingsManualBase::InitializeSettings(); + + const std::shared_ptr<CSettingCategory> category = AddCategory("audiosubtitlesettings", -1); + if (category == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogAudioSettings: unable to setup settings"); + return; + } + + // get all necessary setting groups + const std::shared_ptr<CSettingGroup> groupAudio = AddGroup(category); + if (groupAudio == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogAudioSettings: unable to setup settings"); + return; + } + const std::shared_ptr<CSettingGroup> groupSubtitles = AddGroup(category); + if (groupSubtitles == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogAudioSettings: unable to setup settings"); + return; + } + const std::shared_ptr<CSettingGroup> groupSaveAsDefault = AddGroup(category); + if (groupSaveAsDefault == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogAudioSettings: unable to setup settings"); + return; + } + + bool usePopup = g_SkinInfo->HasSkinFile("DialogSlider.xml"); + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + const CVideoSettings videoSettings = appPlayer->GetVideoSettings(); + if (appPlayer->HasPlayer()) + { + appPlayer->GetAudioCapabilities(m_audioCaps); + } + + // register IsPlayingPassthrough condition + GetSettingsManager()->AddDynamicCondition("IsPlayingPassthrough", IsPlayingPassthrough); + + CSettingDependency dependencyAudioOutputPassthroughDisabled(SettingDependencyType::Enable, GetSettingsManager()); + dependencyAudioOutputPassthroughDisabled.Or() + ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_AUDIO_PASSTHROUGH, "false", SettingDependencyOperator::Equals, false, GetSettingsManager()))) + ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition("IsPlayingPassthrough", "", "", true, GetSettingsManager()))); + SettingDependencies depsAudioOutputPassthroughDisabled; + depsAudioOutputPassthroughDisabled.push_back(dependencyAudioOutputPassthroughDisabled); + + // audio settings + // audio volume setting + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + m_volume = appVolume->GetVolumeRatio(); + std::shared_ptr<CSettingNumber> settingAudioVolume = + AddSlider(groupAudio, SETTING_AUDIO_VOLUME, 13376, SettingLevel::Basic, m_volume, 14054, + CApplicationVolumeHandling::VOLUME_MINIMUM, + CApplicationVolumeHandling::VOLUME_MAXIMUM / 100.0f, + CApplicationVolumeHandling::VOLUME_MAXIMUM); + settingAudioVolume->SetDependencies(depsAudioOutputPassthroughDisabled); + std::static_pointer_cast<CSettingControlSlider>(settingAudioVolume->GetControl())->SetFormatter(SettingFormatterPercentAsDecibel); + + // audio volume amplification setting + if (SupportsAudioFeature(IPC_AUD_AMP)) + { + std::shared_ptr<CSettingNumber> settingAudioVolumeAmplification = AddSlider(groupAudio, SETTING_AUDIO_VOLUME_AMPLIFICATION, 660, SettingLevel::Basic, videoSettings.m_VolumeAmplification, 14054, VOLUME_DRC_MINIMUM * 0.01f, (VOLUME_DRC_MAXIMUM - VOLUME_DRC_MINIMUM) / 6000.0f, VOLUME_DRC_MAXIMUM * 0.01f); + settingAudioVolumeAmplification->SetDependencies(depsAudioOutputPassthroughDisabled); + } + + // downmix: center mix level + { + AddSlider(groupAudio, SETTING_AUDIO_CENTERMIXLEVEL, 39112, SettingLevel::Basic, + videoSettings.m_CenterMixLevel, 14050, -10, 1, 30, + -1, false, false, true, 39113); + } + + // audio delay setting + if (SupportsAudioFeature(IPC_AUD_OFFSET)) + { + std::shared_ptr<CSettingNumber> settingAudioDelay = AddSlider( + groupAudio, SETTING_AUDIO_DELAY, 297, SettingLevel::Basic, videoSettings.m_AudioDelay, 0, + -CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoAudioDelayRange, + AUDIO_DELAY_STEP, + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoAudioDelayRange, 297, + usePopup); + std::static_pointer_cast<CSettingControlSlider>(settingAudioDelay->GetControl())->SetFormatter(SettingFormatterDelay); + } + + // audio stream setting + if (SupportsAudioFeature(IPC_AUD_SELECT_STREAM)) + AddAudioStreams(groupAudio, SETTING_AUDIO_STREAM); + + // audio digital/analog setting + if (SupportsAudioFeature(IPC_AUD_SELECT_OUTPUT)) + { + m_passthrough = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGH); + AddToggle(groupAudio, SETTING_AUDIO_PASSTHROUGH, 348, SettingLevel::Basic, m_passthrough); + } + + // subtitle stream setting + AddButton(groupSaveAsDefault, SETTING_AUDIO_MAKE_DEFAULT, 12376, SettingLevel::Basic); +} + +bool CGUIDialogAudioSettings::SupportsAudioFeature(int feature) +{ + for (Features::iterator itr = m_audioCaps.begin(); itr != m_audioCaps.end(); ++itr) + { + if (*itr == feature || *itr == IPC_AUD_ALL) + return true; + } + + return false; +} + +void CGUIDialogAudioSettings::AddAudioStreams(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingId) +{ + if (group == NULL || settingId.empty()) + return; + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + m_audioStream = appPlayer->GetAudioStream(); + if (m_audioStream < 0) + m_audioStream = 0; + + AddList(group, settingId, 460, SettingLevel::Basic, m_audioStream, AudioStreamsOptionFiller, 460); +} + +bool CGUIDialogAudioSettings::IsPlayingPassthrough(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + return appPlayer->IsPassthrough(); +} + +void CGUIDialogAudioSettings::AudioStreamsOptionFiller(const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + int audioStreamCount = appPlayer->GetAudioStreamCount(); + + std::string strFormat = "{:s} - {:s} - {:d} " + g_localizeStrings.Get(10127); + std::string strUnknown = "[" + g_localizeStrings.Get(13205) + "]"; + + // cycle through each audio stream and add it to our list control + for (int i = 0; i < audioStreamCount; ++i) + { + std::string strItem; + std::string strLanguage; + + AudioStreamInfo info; + appPlayer->GetAudioStreamInfo(i, info); + + if (!g_LangCodeExpander.Lookup(info.language, strLanguage)) + strLanguage = strUnknown; + + if (info.name.length() == 0) + info.name = strUnknown; + + strItem = StringUtils::Format(strFormat, strLanguage, info.name, info.channels); + + strItem += FormatFlags(info.flags); + strItem += StringUtils::Format(" ({}/{})", i + 1, audioStreamCount); + list.emplace_back(strItem, i); + } + + if (list.empty()) + { + list.emplace_back(g_localizeStrings.Get(231), -1); + current = -1; + } +} + +std::string CGUIDialogAudioSettings::SettingFormatterDelay( + const std::shared_ptr<const CSettingControlSlider>& control, + const CVariant& value, + const CVariant& minimum, + const CVariant& step, + const CVariant& maximum) +{ + if (!value.isDouble()) + return ""; + + float fValue = value.asFloat(); + float fStep = step.asFloat(); + + if (fabs(fValue) < 0.5f * fStep) + return StringUtils::Format(g_localizeStrings.Get(22003), 0.0); + if (fValue < 0) + return StringUtils::Format(g_localizeStrings.Get(22004), fabs(fValue)); + + return StringUtils::Format(g_localizeStrings.Get(22005), fValue); +} + +std::string CGUIDialogAudioSettings::SettingFormatterPercentAsDecibel( + const std::shared_ptr<const CSettingControlSlider>& control, + const CVariant& value, + const CVariant& minimum, + const CVariant& step, + const CVariant& maximum) +{ + if (control == NULL || !value.isDouble()) + return ""; + + std::string formatString = control->GetFormatString(); + if (control->GetFormatLabel() > -1) + formatString = g_localizeStrings.Get(control->GetFormatLabel()); + + return StringUtils::Format(formatString, CAEUtil::PercentToGain(value.asFloat())); +} + +std::string CGUIDialogAudioSettings::FormatFlags(StreamFlags flags) +{ + std::vector<std::string> localizedFlags; + if (flags & StreamFlags::FLAG_DEFAULT) + localizedFlags.emplace_back(g_localizeStrings.Get(39105)); + if (flags & StreamFlags::FLAG_FORCED) + localizedFlags.emplace_back(g_localizeStrings.Get(39106)); + if (flags & StreamFlags::FLAG_HEARING_IMPAIRED) + localizedFlags.emplace_back(g_localizeStrings.Get(39107)); + if (flags & StreamFlags::FLAG_VISUAL_IMPAIRED) + localizedFlags.emplace_back(g_localizeStrings.Get(39108)); + if (flags & StreamFlags::FLAG_ORIGINAL) + localizedFlags.emplace_back(g_localizeStrings.Get(39111)); + + std::string formated = StringUtils::Join(localizedFlags, ", "); + + if (!formated.empty()) + formated = StringUtils::Format(" [{}]", formated); + + return formated; +} diff --git a/xbmc/video/dialogs/GUIDialogAudioSettings.h b/xbmc/video/dialogs/GUIDialogAudioSettings.h new file mode 100644 index 0000000..de69b77 --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogAudioSettings.h @@ -0,0 +1,82 @@ +/* + * 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. + */ + +#pragma once + +#include "cores/VideoPlayer/Interface/StreamInfo.h" +#include "settings/dialogs/GUIDialogSettingsManualBase.h" + +#include <string> +#include <utility> +#include <vector> + +class CVariant; +struct IntegerSettingOption; + +class CGUIDialogAudioSettings : public CGUIDialogSettingsManualBase +{ +public: + CGUIDialogAudioSettings(); + ~CGUIDialogAudioSettings() override; + + // specialization of CGUIWindow + void FrameMove() override; + + static std::string FormatDelay(float value, float interval); + static std::string FormatDecibel(float value); + static std::string FormatPercentAsDecibel(float value); + +protected: + // implementations of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + + // specialization of CGUIDialogSettingsBase + bool AllowResettingSettings() const override { return false; } + bool Save() override; + void SetupView() override; + + // specialization of CGUIDialogSettingsManualBase + void InitializeSettings() override; + + bool SupportsAudioFeature(int feature); + + void AddAudioStreams(const std::shared_ptr<CSettingGroup>& group, const std::string& settingId); + + static bool IsPlayingPassthrough(const std::string& condition, + const std::string& value, + const std::shared_ptr<const CSetting>& setting, + void* data); + + static void AudioStreamsOptionFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + + static std::string SettingFormatterDelay( + const std::shared_ptr<const CSettingControlSlider>& control, + const CVariant& value, + const CVariant& minimum, + const CVariant& step, + const CVariant& maximum); + static std::string SettingFormatterPercentAsDecibel( + const std::shared_ptr<const CSettingControlSlider>& control, + const CVariant& value, + const CVariant& minimum, + const CVariant& step, + const CVariant& maximum); + + float m_volume; + int m_audioStream; + bool m_passthrough = false; + + typedef std::vector<int> Features; + Features m_audioCaps; +private: + static std::string FormatFlags(StreamFlags flags); +}; diff --git a/xbmc/video/dialogs/GUIDialogCMSSettings.cpp b/xbmc/video/dialogs/GUIDialogCMSSettings.cpp new file mode 100644 index 0000000..ac1a7ad --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogCMSSettings.cpp @@ -0,0 +1,233 @@ +/* + * 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 "GUIDialogCMSSettings.h" + +#include "FileItem.h" +#include "GUIPassword.h" +#include "ServiceBroker.h" +#include "addons/Skin.h" +#include "cores/VideoPlayer/VideoRenderers/ColorManager.h" +#include "cores/VideoPlayer/VideoRenderers/RenderManager.h" +#include "filesystem/Directory.h" +#include "guilib/GUIWindowManager.h" +#include "profiles/ProfileManager.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingsManager.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" + +#include <vector> + +#define SETTING_VIDEO_CMSENABLE "videoscreen.cmsenabled" +#define SETTING_VIDEO_CMSMODE "videoscreen.cmsmode" +#define SETTING_VIDEO_CMS3DLUT "videoscreen.cms3dlut" +#define SETTING_VIDEO_CMSWHITEPOINT "videoscreen.cmswhitepoint" +#define SETTING_VIDEO_CMSPRIMARIES "videoscreen.cmsprimaries" +#define SETTING_VIDEO_CMSGAMMAMODE "videoscreen.cmsgammamode" +#define SETTING_VIDEO_CMSGAMMA "videoscreen.cmsgamma" +#define SETTING_VIDEO_CMSLUTSIZE "videoscreen.cmslutsize" + +CGUIDialogCMSSettings::CGUIDialogCMSSettings() + : CGUIDialogSettingsManualBase(WINDOW_DIALOG_CMS_OSD_SETTINGS, "DialogSettings.xml") +{ } + +CGUIDialogCMSSettings::~CGUIDialogCMSSettings() = default; + +void CGUIDialogCMSSettings::SetupView() +{ + CGUIDialogSettingsManualBase::SetupView(); + + SetHeading(36560); + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_OKAY_BUTTON); + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_CUSTOM_BUTTON); + SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 15067); +} + +void CGUIDialogCMSSettings::InitializeSettings() +{ + CGUIDialogSettingsManualBase::InitializeSettings(); + + const std::shared_ptr<CSettingCategory> category = AddCategory("cms", -1); + if (category == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogCMSSettings: unable to setup settings"); + return; + } + + // get all necessary setting groups + const std::shared_ptr<CSettingGroup> groupColorManagement = AddGroup(category); + if (groupColorManagement == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogCMSSettings: unable to setup settings"); + return; + } + + bool usePopup = g_SkinInfo->HasSkinFile("DialogSlider.xml"); + + TranslatableIntegerSettingOptions entries; + + // create "depsCmsEnabled" for settings depending on CMS being enabled + CSettingDependency dependencyCmsEnabled(SettingDependencyType::Enable, GetSettingsManager()); + dependencyCmsEnabled.Or() + ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_VIDEO_CMSENABLE, "true", SettingDependencyOperator::Equals, false, GetSettingsManager()))); + SettingDependencies depsCmsEnabled; + depsCmsEnabled.push_back(dependencyCmsEnabled); + + // create "depsCms3dlut" for 3dlut settings + CSettingDependency dependencyCms3dlut(SettingDependencyType::Visible, GetSettingsManager()); + dependencyCms3dlut.And() + ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_VIDEO_CMSMODE, std::to_string(CMS_MODE_3DLUT), SettingDependencyOperator::Equals, false, GetSettingsManager()))); + SettingDependencies depsCms3dlut; + depsCms3dlut.push_back(dependencyCmsEnabled); + depsCms3dlut.push_back(dependencyCms3dlut); + + // create "depsCmsIcc" for display settings with icc profile + CSettingDependency dependencyCmsIcc(SettingDependencyType::Visible, GetSettingsManager()); + dependencyCmsIcc.And() + ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_VIDEO_CMSMODE, std::to_string(CMS_MODE_PROFILE), SettingDependencyOperator::Equals, false, GetSettingsManager()))); + SettingDependencies depsCmsIcc; + depsCmsIcc.push_back(dependencyCmsEnabled); + depsCmsIcc.push_back(dependencyCmsIcc); + + // create "depsCmsGamma" for effective gamma adjustment (not available with bt.1886) + CSettingDependency dependencyCmsGamma(SettingDependencyType::Visible, GetSettingsManager()); + dependencyCmsGamma.And() + ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_VIDEO_CMSGAMMAMODE, std::to_string(CMS_TRC_BT1886), SettingDependencyOperator::Equals, true, GetSettingsManager()))); + SettingDependencies depsCmsGamma; + depsCmsGamma.push_back(dependencyCmsEnabled); + depsCmsGamma.push_back(dependencyCmsIcc); + depsCmsGamma.push_back(dependencyCmsGamma); + + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + + // color management settings + AddToggle(groupColorManagement, SETTING_VIDEO_CMSENABLE, 36560, SettingLevel::Basic, settings->GetBool(SETTING_VIDEO_CMSENABLE)); + + int currentMode = settings->GetInt(SETTING_VIDEO_CMSMODE); + entries.clear(); + // entries.push_back(TranslatableIntegerSettingOption(16039, CMS_MODE_OFF)); // FIXME: get from CMS class + entries.push_back(TranslatableIntegerSettingOption(36580, CMS_MODE_3DLUT)); +#ifdef HAVE_LCMS2 + entries.push_back(TranslatableIntegerSettingOption(36581, CMS_MODE_PROFILE)); +#endif + std::shared_ptr<CSettingInt> settingCmsMode = AddSpinner(groupColorManagement, SETTING_VIDEO_CMSMODE, 36562, SettingLevel::Basic, currentMode, entries); + settingCmsMode->SetDependencies(depsCmsEnabled); + + std::string current3dLUT = settings->GetString(SETTING_VIDEO_CMS3DLUT); + std::shared_ptr<CSettingString> settingCms3dlut = AddList(groupColorManagement, SETTING_VIDEO_CMS3DLUT, 36564, SettingLevel::Basic, current3dLUT, Cms3dLutsFiller, 36564); + settingCms3dlut->SetDependencies(depsCms3dlut); + + // display settings + int currentWhitepoint = settings->GetInt(SETTING_VIDEO_CMSWHITEPOINT); + entries.clear(); + entries.push_back(TranslatableIntegerSettingOption(36586, CMS_WHITEPOINT_D65)); + entries.push_back(TranslatableIntegerSettingOption(36587, CMS_WHITEPOINT_D93)); + std::shared_ptr<CSettingInt> settingCmsWhitepoint = AddSpinner(groupColorManagement, SETTING_VIDEO_CMSWHITEPOINT, 36568, SettingLevel::Basic, currentWhitepoint, entries); + settingCmsWhitepoint->SetDependencies(depsCmsIcc); + + int currentPrimaries = settings->GetInt(SETTING_VIDEO_CMSPRIMARIES); + entries.clear(); + entries.push_back(TranslatableIntegerSettingOption(36588, CMS_PRIMARIES_AUTO)); + entries.push_back(TranslatableIntegerSettingOption(36589, CMS_PRIMARIES_BT709)); + entries.push_back(TranslatableIntegerSettingOption(36579, CMS_PRIMARIES_BT2020)); + entries.push_back(TranslatableIntegerSettingOption(36590, CMS_PRIMARIES_170M)); + entries.push_back(TranslatableIntegerSettingOption(36591, CMS_PRIMARIES_BT470M)); + entries.push_back(TranslatableIntegerSettingOption(36592, CMS_PRIMARIES_BT470BG)); + entries.push_back(TranslatableIntegerSettingOption(36593, CMS_PRIMARIES_240M)); + std::shared_ptr<CSettingInt> settingCmsPrimaries = AddSpinner(groupColorManagement, SETTING_VIDEO_CMSPRIMARIES, 36570, SettingLevel::Basic, currentPrimaries, entries); + settingCmsPrimaries->SetDependencies(depsCmsIcc); + + int currentGammaMode = settings->GetInt(SETTING_VIDEO_CMSGAMMAMODE); + entries.clear(); + entries.push_back(TranslatableIntegerSettingOption(36582, CMS_TRC_BT1886)); + entries.push_back(TranslatableIntegerSettingOption(36583, CMS_TRC_INPUT_OFFSET)); + entries.push_back(TranslatableIntegerSettingOption(36584, CMS_TRC_OUTPUT_OFFSET)); + entries.push_back(TranslatableIntegerSettingOption(36585, CMS_TRC_ABSOLUTE)); + std::shared_ptr<CSettingInt> settingCmsGammaMode = AddSpinner(groupColorManagement, SETTING_VIDEO_CMSGAMMAMODE, 36572, SettingLevel::Basic, currentGammaMode, entries); + settingCmsGammaMode->SetDependencies(depsCmsIcc); + + float currentGamma = settings->GetInt(SETTING_VIDEO_CMSGAMMA)/100.0f; + if (currentGamma == 0.0f) + currentGamma = 2.20f; + std::shared_ptr<CSettingNumber> settingCmsGamma = AddSlider(groupColorManagement, SETTING_VIDEO_CMSGAMMA, 36574, SettingLevel::Basic, currentGamma, 36597, 1.6, 0.05, 2.8, 36574, usePopup); + settingCmsGamma->SetDependencies(depsCmsGamma); + + int currentLutSize = settings->GetInt(SETTING_VIDEO_CMSLUTSIZE); + entries.clear(); + entries.push_back(TranslatableIntegerSettingOption(36594, 4)); + entries.push_back(TranslatableIntegerSettingOption(36595, 6)); + entries.push_back(TranslatableIntegerSettingOption(36596, 8)); + std::shared_ptr<CSettingInt> settingCmsLutSize = AddSpinner(groupColorManagement, SETTING_VIDEO_CMSLUTSIZE, 36576, SettingLevel::Basic, currentLutSize, entries); + settingCmsLutSize->SetDependencies(depsCmsIcc); +} + +void CGUIDialogCMSSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + const std::string &settingId = setting->GetId(); + if (settingId == SETTING_VIDEO_CMSENABLE) + CServiceBroker::GetSettingsComponent()->GetSettings()->SetBool(SETTING_VIDEO_CMSENABLE, (std::static_pointer_cast<const CSettingBool>(setting)->GetValue())); + else if (settingId == SETTING_VIDEO_CMSMODE) + CServiceBroker::GetSettingsComponent()->GetSettings()->SetInt(SETTING_VIDEO_CMSMODE, std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + else if (settingId == SETTING_VIDEO_CMS3DLUT) + CServiceBroker::GetSettingsComponent()->GetSettings()->SetString(SETTING_VIDEO_CMS3DLUT, std::static_pointer_cast<const CSettingString>(setting)->GetValue()); + else if (settingId == SETTING_VIDEO_CMSWHITEPOINT) + CServiceBroker::GetSettingsComponent()->GetSettings()->SetInt(SETTING_VIDEO_CMSWHITEPOINT, std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + else if (settingId == SETTING_VIDEO_CMSPRIMARIES) + CServiceBroker::GetSettingsComponent()->GetSettings()->SetInt(SETTING_VIDEO_CMSPRIMARIES, std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + else if (settingId == SETTING_VIDEO_CMSGAMMAMODE) + CServiceBroker::GetSettingsComponent()->GetSettings()->SetInt(SETTING_VIDEO_CMSGAMMAMODE, std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + else if (settingId == SETTING_VIDEO_CMSGAMMA) + CServiceBroker::GetSettingsComponent()->GetSettings()->SetInt(SETTING_VIDEO_CMSGAMMA, static_cast<float>(std::static_pointer_cast<const CSettingNumber>(setting)->GetValue())*100); + else if (settingId == SETTING_VIDEO_CMSLUTSIZE) + CServiceBroker::GetSettingsComponent()->GetSettings()->SetInt(SETTING_VIDEO_CMSLUTSIZE, std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); +} + +bool CGUIDialogCMSSettings::OnBack(int actionID) +{ + Save(); + return CGUIDialogSettingsBase::OnBack(actionID); +} + +bool CGUIDialogCMSSettings::Save() +{ + CLog::Log(LOGINFO, "CGUIDialogCMSSettings: Save() called"); + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + return true; +} + +void CGUIDialogCMSSettings::Cms3dLutsFiller(const SettingConstPtr& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data) +{ + // get 3dLut directory from settings + CFileItemList items; + + // list .3dlut files + std::string current3dlut = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(SETTING_VIDEO_CMS3DLUT); + if (!current3dlut.empty()) + current3dlut = URIUtils::GetDirectory(current3dlut); + XFILE::CDirectory::GetDirectory(current3dlut, items, ".3dlut", XFILE::DIR_FLAG_DEFAULTS); + + for (int i = 0; i < items.Size(); i++) + { + list.emplace_back(items[i]->GetLabel(), items[i]->GetPath()); + } +} diff --git a/xbmc/video/dialogs/GUIDialogCMSSettings.h b/xbmc/video/dialogs/GUIDialogCMSSettings.h new file mode 100644 index 0000000..e718b29 --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogCMSSettings.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#pragma once + +#include "settings/dialogs/GUIDialogSettingsManualBase.h" + +struct StringSettingOption; + +class CGUIDialogCMSSettings : public CGUIDialogSettingsManualBase +{ +public: + CGUIDialogCMSSettings(); + ~CGUIDialogCMSSettings() override; + +protected: + // implementations of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + + // specialization of CGUIDialogSettingsBase + bool AllowResettingSettings() const override { return false; } + bool OnBack(int actionID) override; + bool Save() override; + void SetupView() override; + + // specialization of CGUIDialogSettingsManualBase + void InitializeSettings() override; + +private: + static void Cms3dLutsFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<StringSettingOption>& list, + std::string& current, + void* data); +}; diff --git a/xbmc/video/dialogs/GUIDialogFullScreenInfo.cpp b/xbmc/video/dialogs/GUIDialogFullScreenInfo.cpp new file mode 100644 index 0000000..70ebafe --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogFullScreenInfo.cpp @@ -0,0 +1,30 @@ +/* + * 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 "GUIDialogFullScreenInfo.h" + +#include "input/Key.h" + +CGUIDialogFullScreenInfo::CGUIDialogFullScreenInfo(void) + : CGUIDialog(WINDOW_DIALOG_FULLSCREEN_INFO, "DialogFullScreenInfo.xml") +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogFullScreenInfo::~CGUIDialogFullScreenInfo(void) = default; + +bool CGUIDialogFullScreenInfo::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_SHOW_INFO) + { + Close(); + return true; + } + return CGUIDialog::OnAction(action); +} + diff --git a/xbmc/video/dialogs/GUIDialogFullScreenInfo.h b/xbmc/video/dialogs/GUIDialogFullScreenInfo.h new file mode 100644 index 0000000..ca5b5e8 --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogFullScreenInfo.h @@ -0,0 +1,21 @@ +/* + * 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. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class CGUIDialogFullScreenInfo : + public CGUIDialog +{ +public: + CGUIDialogFullScreenInfo(void); + ~CGUIDialogFullScreenInfo(void) override; + bool OnAction(const CAction &action) override; +}; + diff --git a/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp new file mode 100644 index 0000000..24f2618 --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp @@ -0,0 +1,426 @@ +/* + * 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 "GUIDialogSubtitleSettings.h" + +#include "FileItem.h" +#include "GUIDialogSubtitles.h" +#include "GUIPassword.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "addons/Skin.h" +#include "addons/VFSEntry.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "cores/IPlayer.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "dialogs/GUIDialogYesNo.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "profiles/ProfileManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSettings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingsManager.h" +#include "utils/FileUtils.h" +#include "utils/LangCodeExpander.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" + +#include <string> +#include <vector> + +#define SETTING_SUBTITLE_ENABLE "subtitles.enable" +#define SETTING_SUBTITLE_DELAY "subtitles.delay" +#define SETTING_SUBTITLE_STREAM "subtitles.stream" +#define SETTING_SUBTITLE_BROWSER "subtitles.browser" +#define SETTING_SUBTITLE_SEARCH "subtitles.search" +#define SETTING_MAKE_DEFAULT "audio.makedefault" + +CGUIDialogSubtitleSettings::CGUIDialogSubtitleSettings() + : CGUIDialogSettingsManualBase(WINDOW_DIALOG_SUBTITLE_OSD_SETTINGS, "DialogSettings.xml") +{ } + +CGUIDialogSubtitleSettings::~CGUIDialogSubtitleSettings() = default; + +void CGUIDialogSubtitleSettings::FrameMove() +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + const CVideoSettings videoSettings = appPlayer->GetVideoSettings(); + + // these settings can change on the fly + //! @todo m_settingsManager->SetBool(SETTING_SUBTITLE_ENABLE, g_application.GetAppPlayer().GetSubtitleVisible()); + // \-> Unless subtitle visibility can change on the fly, while Dialog is up, this code should be removed. + GetSettingsManager()->SetNumber(SETTING_SUBTITLE_DELAY, + static_cast<double>(videoSettings.m_SubtitleDelay)); + //! @todo (needs special handling): m_settingsManager->SetInt(SETTING_SUBTITLE_STREAM, g_application.GetAppPlayer().GetSubtitle()); + } + + CGUIDialogSettingsManualBase::FrameMove(); +} + +bool CGUIDialogSubtitleSettings::OnMessage(CGUIMessage& message) +{ + if (message.GetMessage() == GUI_MSG_SUBTITLE_DOWNLOADED) + { + Close(); + } + return CGUIDialogSettingsManualBase::OnMessage(message); +} + +void CGUIDialogSubtitleSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + const std::string &settingId = setting->GetId(); + if (settingId == SETTING_SUBTITLE_ENABLE) + { + bool value = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + if (value) + { + // Ensure that we use/store the subtitle stream the user currently sees in the dialog. + appPlayer->SetSubtitle(m_subtitleStream); + } + appPlayer->SetSubtitleVisible(value); + } + else if (settingId == SETTING_SUBTITLE_DELAY) + { + float value = static_cast<float>(std::static_pointer_cast<const CSettingNumber>(setting)->GetValue()); + appPlayer->SetSubTitleDelay(value); + } + else if (settingId == SETTING_SUBTITLE_STREAM) + { + m_subtitleStream = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + appPlayer->SetSubtitle(m_subtitleStream); + } +} + +std::string CGUIDialogSubtitleSettings::BrowseForSubtitle() +{ + std::string extras; + for (const auto& vfsAddon : CServiceBroker::GetVFSAddonCache().GetAddonInstances()) + { + if (vfsAddon->ID() == "vfs.rar" || vfsAddon->ID() == "vfs.libarchive") + extras += '|' + vfsAddon->GetExtensions(); + } + + std::string strPath; + if (URIUtils::IsInRAR(g_application.CurrentFileItem().GetPath()) || URIUtils::IsInZIP(g_application.CurrentFileItem().GetPath())) + { + strPath = CURL(g_application.CurrentFileItem().GetPath()).GetHostName(); + } + else if (!URIUtils::IsPlugin(g_application.CurrentFileItem().GetPath())) + { + strPath = g_application.CurrentFileItem().GetPath(); + } + + std::string strMask = + ".utf|.utf8|.utf-8|.sub|.srt|.smi|.rt|.txt|.ssa|.aqt|.jss|.ass|.vtt|.idx|.zip|.sup"; + + if (g_application.GetCurrentPlayer() == "VideoPlayer") + strMask = ".srt|.zip|.ifo|.smi|.sub|.idx|.ass|.ssa|.vtt|.txt|.sup"; + + strMask += extras; + + VECSOURCES shares(*CMediaSourceSettings::GetInstance().GetSources("video")); + if (CMediaSettings::GetInstance().GetAdditionalSubtitleDirectoryChecked() != -1 && !CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SUBTITLES_CUSTOMPATH).empty()) + { + CMediaSource share; + std::vector<std::string> paths; + if (!strPath.empty()) + { + paths.push_back(URIUtils::GetDirectory(strPath)); + } + paths.push_back(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SUBTITLES_CUSTOMPATH)); + share.FromNameAndPaths("video",g_localizeStrings.Get(21367),paths); + shares.push_back(share); + strPath = share.strPath; + URIUtils::AddSlashAtEnd(strPath); + } + + if (CGUIDialogFileBrowser::ShowAndGetFile(shares, strMask, g_localizeStrings.Get(293), strPath, false, true)) // "subtitles" + { + if (URIUtils::HasExtension(strPath, ".sub")) + { + if (CFileUtils::Exists(URIUtils::ReplaceExtension(strPath, ".idx"))) + strPath = URIUtils::ReplaceExtension(strPath, ".idx"); + } + + return strPath; + } + + return ""; +} + +void CGUIDialogSubtitleSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingAction(setting); + + const std::string &settingId = setting->GetId(); + if (settingId == SETTING_SUBTITLE_BROWSER) + { + std::string strPath = BrowseForSubtitle(); + if (!strPath.empty()) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + appPlayer->AddSubtitle(strPath); + Close(); + } + } + else if (settingId == SETTING_SUBTITLE_SEARCH) + { + auto dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSubtitles>(WINDOW_DIALOG_SUBTITLES); + if (dialog) + { + dialog->Open(); + m_subtitleStreamSetting->UpdateDynamicOptions(); + } + } + else if (settingId == SETTING_MAKE_DEFAULT) + Save(); +} + +bool CGUIDialogSubtitleSettings::Save() +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + if (!g_passwordManager.CheckSettingLevelLock(SettingLevel::Expert) && + profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE) + return true; + + // prompt user if they are sure + if (!CGUIDialogYesNo::ShowAndGetInput(CVariant{12376}, CVariant{12377})) + return true; + + // reset the settings + CVideoDatabase db; + if (!db.Open()) + return true; + + db.EraseAllVideoSettings(); + db.Close(); + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + CMediaSettings::GetInstance().GetDefaultVideoSettings() = appPlayer->GetVideoSettings(); + CMediaSettings::GetInstance().GetDefaultVideoSettings().m_SubtitleStream = -1; + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + + return true; +} + +void CGUIDialogSubtitleSettings::SetupView() +{ + CGUIDialogSettingsManualBase::SetupView(); + + SetHeading(24133); + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_OKAY_BUTTON); + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_CUSTOM_BUTTON); + SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 15067); +} + +void CGUIDialogSubtitleSettings::InitializeSettings() +{ + CGUIDialogSettingsManualBase::InitializeSettings(); + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + const std::shared_ptr<CSettingCategory> category = AddCategory("audiosubtitlesettings", -1); + if (category == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogSubtitleSettings: unable to setup settings"); + return; + } + + // get all necessary setting groups + const std::shared_ptr<CSettingGroup> groupAudio = AddGroup(category); + if (groupAudio == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogSubtitleSettings: unable to setup settings"); + return; + } + const std::shared_ptr<CSettingGroup> groupSubtitles = AddGroup(category); + if (groupSubtitles == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogSubtitleSettings: unable to setup settings"); + return; + } + const std::shared_ptr<CSettingGroup> groupSaveAsDefault = AddGroup(category); + if (groupSaveAsDefault == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogSubtitleSettings: unable to setup settings"); + return; + } + + bool usePopup = g_SkinInfo->HasSkinFile("DialogSlider.xml"); + + const CVideoSettings videoSettings = appPlayer->GetVideoSettings(); + + if (appPlayer->HasPlayer()) + { + appPlayer->GetSubtitleCapabilities(m_subtitleCapabilities); + } + + // subtitle settings + m_subtitleVisible = appPlayer->GetSubtitleVisible(); + + // subtitle enabled setting + AddToggle(groupSubtitles, SETTING_SUBTITLE_ENABLE, 13397, SettingLevel::Basic, m_subtitleVisible); + + // subtitle delay setting + if (SupportsSubtitleFeature(IPC_SUBS_OFFSET)) + { + std::shared_ptr<CSettingNumber> settingSubtitleDelay = AddSlider(groupSubtitles, SETTING_SUBTITLE_DELAY, 22006, SettingLevel::Basic, videoSettings.m_SubtitleDelay, 0, -CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoSubsDelayRange, 0.1f, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoSubsDelayRange, 22006, usePopup); + std::static_pointer_cast<CSettingControlSlider>(settingSubtitleDelay->GetControl())->SetFormatter(SettingFormatterDelay); + } + + // subtitle stream setting + if (SupportsSubtitleFeature(IPC_SUBS_SELECT)) + AddSubtitleStreams(groupSubtitles, SETTING_SUBTITLE_STREAM); + + // subtitle browser setting + if (SupportsSubtitleFeature(IPC_SUBS_EXTERNAL)) + AddButton(groupSubtitles, SETTING_SUBTITLE_BROWSER, 13250, SettingLevel::Basic); + + AddButton(groupSubtitles, SETTING_SUBTITLE_SEARCH, 24134, SettingLevel::Basic); + + // subtitle stream setting + AddButton(groupSaveAsDefault, SETTING_MAKE_DEFAULT, 12376, SettingLevel::Basic); +} + +bool CGUIDialogSubtitleSettings::SupportsSubtitleFeature(int feature) +{ + for (auto item : m_subtitleCapabilities) + { + if (item == feature || item == IPC_SUBS_ALL) + return true; + } + return false; +} + +void CGUIDialogSubtitleSettings::AddSubtitleStreams(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingId) +{ + if (group == NULL || settingId.empty()) + return; + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + m_subtitleStream = appPlayer->GetSubtitle(); + if (m_subtitleStream < 0) + m_subtitleStream = 0; + + m_subtitleStreamSetting = AddList(group, settingId, 462, SettingLevel::Basic, m_subtitleStream, SubtitleStreamsOptionFiller, 462); +} + +void CGUIDialogSubtitleSettings::SubtitleStreamsOptionFiller( + const SettingConstPtr& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + int subtitleStreamCount = appPlayer->GetSubtitleCount(); + + // cycle through each subtitle and add it to our entry list + for (int i = 0; i < subtitleStreamCount; ++i) + { + SubtitleStreamInfo info; + appPlayer->GetSubtitleStreamInfo(i, info); + + std::string strItem; + std::string strLanguage; + + if (!g_LangCodeExpander.Lookup(info.language, strLanguage)) + strLanguage = g_localizeStrings.Get(13205); // Unknown + + if (info.name.length() == 0) + strItem = strLanguage; + else + strItem = StringUtils::Format("{} - {}", strLanguage, info.name); + + strItem += FormatFlags(info.flags); + strItem += StringUtils::Format(" ({}/{})", i + 1, subtitleStreamCount); + + list.emplace_back(strItem, i); + } + + // no subtitle streams - just add a "None" entry + if (list.empty()) + { + list.emplace_back(g_localizeStrings.Get(231), -1); + current = -1; + } +} + +std::string CGUIDialogSubtitleSettings::SettingFormatterDelay( + const std::shared_ptr<const CSettingControlSlider>& control, + const CVariant& value, + const CVariant& minimum, + const CVariant& step, + const CVariant& maximum) +{ + if (!value.isDouble()) + return ""; + + float fValue = value.asFloat(); + float fStep = step.asFloat(); + + if (fabs(fValue) < 0.5f * fStep) + return StringUtils::Format(g_localizeStrings.Get(22003), 0.0); + if (fValue < 0) + return StringUtils::Format(g_localizeStrings.Get(22004), fabs(fValue)); + + return StringUtils::Format(g_localizeStrings.Get(22005), fValue); +} + +std::string CGUIDialogSubtitleSettings::FormatFlags(StreamFlags flags) +{ + std::vector<std::string> localizedFlags; + if (flags & StreamFlags::FLAG_DEFAULT) + localizedFlags.emplace_back(g_localizeStrings.Get(39105)); + if (flags & StreamFlags::FLAG_FORCED) + localizedFlags.emplace_back(g_localizeStrings.Get(39106)); + if (flags & StreamFlags::FLAG_HEARING_IMPAIRED) + localizedFlags.emplace_back(g_localizeStrings.Get(39107)); + if (flags & StreamFlags::FLAG_VISUAL_IMPAIRED) + localizedFlags.emplace_back(g_localizeStrings.Get(39108)); + + std::string formated = StringUtils::Join(localizedFlags, ", "); + + if (!formated.empty()) + formated = StringUtils::Format(" [{}]", formated); + + return formated; +} diff --git a/xbmc/video/dialogs/GUIDialogSubtitleSettings.h b/xbmc/video/dialogs/GUIDialogSubtitleSettings.h new file mode 100644 index 0000000..65216ed --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogSubtitleSettings.h @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#pragma once + +#include "cores/VideoPlayer/Interface/StreamInfo.h" +#include "settings/dialogs/GUIDialogSettingsManualBase.h" + +#include <string> +#include <utility> +#include <vector> + +class CVariant; +struct IntegerSettingOption; + +class CGUIDialogSubtitleSettings : public CGUIDialogSettingsManualBase +{ +public: + CGUIDialogSubtitleSettings(); + ~CGUIDialogSubtitleSettings() override; + bool OnMessage(CGUIMessage& message) override; + + // specialization of CGUIWindow + void FrameMove() override; + + static std::string BrowseForSubtitle(); + +protected: + // implementations of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + + // specialization of CGUIDialogSettingsBase + bool AllowResettingSettings() const override { return false; } + bool Save() override; + void SetupView() override; + + // specialization of CGUIDialogSettingsManualBase + void InitializeSettings() override; + +private: + bool SupportsSubtitleFeature(int feature); + + void AddSubtitleStreams(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingId); + + int m_subtitleStream; + bool m_subtitleVisible; + std::shared_ptr<CSettingInt> m_subtitleStreamSetting; + + std::vector<int> m_subtitleCapabilities; + static std::string FormatFlags(StreamFlags flags); + + static void SubtitleStreamsOptionFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + + static std::string SettingFormatterDelay( + const std::shared_ptr<const CSettingControlSlider>& control, + const CVariant& value, + const CVariant& minimum, + const CVariant& step, + const CVariant& maximum); +}; diff --git a/xbmc/video/dialogs/GUIDialogSubtitles.cpp b/xbmc/video/dialogs/GUIDialogSubtitles.cpp new file mode 100644 index 0000000..600c6da --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogSubtitles.cpp @@ -0,0 +1,703 @@ +/* + * 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 "GUIDialogSubtitles.h" + +#include "FileItem.h" +#include "LangInfo.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "addons/gui/GUIDialogAddonSettings.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "cores/IPlayer.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "filesystem/AddonsDirectory.h" +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "filesystem/SpecialProtocol.h" +#include "filesystem/StackDirectory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/actions/ActionIDs.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "utils/JobManager.h" +#include "utils/LangCodeExpander.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" + +#include <mutex> + +using namespace ADDON; +using namespace XFILE; + +namespace +{ +constexpr int CONTROL_NAMELABEL = 100; +constexpr int CONTROL_NAMELOGO = 110; +constexpr int CONTROL_SUBLIST = 120; +constexpr int CONTROL_SUBSEXIST = 130; +constexpr int CONTROL_SUBSTATUS = 140; +constexpr int CONTROL_SERVICELIST = 150; +constexpr int CONTROL_MANUALSEARCH = 160; + +enum class SUBTITLE_SERVICE_CONTEXT_BUTTONS +{ + ADDON_SETTINGS, + ADDON_DISABLE +}; +} // namespace + +/*! \brief simple job to retrieve a directory and store a string (language) + */ +class CSubtitlesJob: public CJob +{ +public: + CSubtitlesJob(const CURL &url, const std::string &language) : m_url(url), m_language(language) + { + m_items = new CFileItemList; + } + ~CSubtitlesJob() override + { + delete m_items; + } + bool DoWork() override + { + CDirectory::GetDirectory(m_url.Get(), *m_items, "", DIR_FLAG_DEFAULTS); + return true; + } + bool operator==(const CJob *job) const override + { + if (strcmp(job->GetType(),GetType()) == 0) + { + const CSubtitlesJob* rjob = dynamic_cast<const CSubtitlesJob*>(job); + if (rjob) + { + return m_url.Get() == rjob->m_url.Get() && + m_language == rjob->m_language; + } + } + return false; + } + const CFileItemList *GetItems() const { return m_items; } + const CURL &GetURL() const { return m_url; } + const std::string &GetLanguage() const { return m_language; } +private: + CURL m_url; + CFileItemList *m_items; + std::string m_language; +}; + +CGUIDialogSubtitles::CGUIDialogSubtitles(void) + : CGUIDialog(WINDOW_DIALOG_SUBTITLES, "DialogSubtitles.xml") + , m_subtitles(new CFileItemList) + , m_serviceItems(new CFileItemList) +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogSubtitles::~CGUIDialogSubtitles(void) +{ + CancelJobs(); + delete m_subtitles; + delete m_serviceItems; +} + +bool CGUIDialogSubtitles::OnMessage(CGUIMessage& message) +{ + if (message.GetMessage() == GUI_MSG_CLICKED) + { + int iControl = message.GetSenderId(); + bool selectAction = (message.GetParam1() == ACTION_SELECT_ITEM || + message.GetParam1() == ACTION_MOUSE_LEFT_CLICK); + + bool contextMenuAction = (message.GetParam1() == ACTION_CONTEXT_MENU || + message.GetParam1() == ACTION_MOUSE_RIGHT_CLICK); + + if (selectAction && iControl == CONTROL_SUBLIST) + { + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_SUBLIST); + OnMessage(msg); + + int item = msg.GetParam1(); + if (item >= 0 && item < m_subtitles->Size()) + Download(*m_subtitles->Get(item)); + return true; + } + else if (selectAction && iControl == CONTROL_SERVICELIST) + { + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_SERVICELIST); + OnMessage(msg); + + int item = msg.GetParam1(); + if (item >= 0 && item < m_serviceItems->Size()) + { + SetService(m_serviceItems->Get(item)->GetProperty("Addon.ID").asString()); + Search(); + } + return true; + } + else if (contextMenuAction && iControl == CONTROL_SERVICELIST) + { + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, GetID(), CONTROL_SERVICELIST); + OnMessage(msg); + + const int itemIdx = msg.GetParam1(); + if (itemIdx >= 0 && itemIdx < m_serviceItems->Size()) + { + OnSubtitleServiceContextMenu(itemIdx); + } + } + else if (iControl == CONTROL_MANUALSEARCH) + { + //manual search + if (CGUIKeyboardFactory::ShowAndGetInput(m_strManualSearch, CVariant{g_localizeStrings.Get(24121)}, true)) + { + Search(m_strManualSearch); + return true; + } + } + } + else if (message.GetMessage() == GUI_MSG_WINDOW_DEINIT) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + // Resume the video if the user has requested it + if (appPlayer->IsPaused() && m_pausedOnRun) + appPlayer->Pause(); + + CGUIDialog::OnMessage(message); + + ClearSubtitles(); + ClearServices(); + return true; + } + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogSubtitles::OnInitWindow() +{ + // Pause the video if the user has requested it + m_pausedOnRun = false; + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_SUBTITLES_PAUSEONSEARCH) && + !appPlayer->IsPaused()) + { + appPlayer->Pause(); + m_pausedOnRun = true; + } + + FillServices(); + CGUIWindow::OnInitWindow(); + Search(); +} + +void CGUIDialogSubtitles::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) +{ + if (m_bInvalidated) + { + // take copies of our variables to ensure we don't hold the lock for long. + std::string status; + CFileItemList subs; + { + std::unique_lock<CCriticalSection> lock(m_critsection); + status = m_status; + subs.Assign(*m_subtitles); + } + SET_CONTROL_LABEL(CONTROL_SUBSTATUS, status); + + if (m_updateSubsList) + { + CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), CONTROL_SUBLIST, 0, 0, &subs); + OnMessage(message); + if (!subs.IsEmpty()) + { + // focus subtitles list + CGUIMessage msg(GUI_MSG_SETFOCUS, GetID(), CONTROL_SUBLIST); + OnMessage(msg); + } + m_updateSubsList = false; + } + + int control = GetFocusedControlID(); + // nothing has focus + if (!control) + { + CGUIMessage msg(GUI_MSG_SETFOCUS, GetID(), m_subtitles->IsEmpty() ? + CONTROL_SERVICELIST : CONTROL_SUBLIST); + OnMessage(msg); + } + // subs list is focused but we have no subs + else if (control == CONTROL_SUBLIST && m_subtitles->IsEmpty()) + { + CGUIMessage msg(GUI_MSG_SETFOCUS, GetID(), CONTROL_SERVICELIST); + OnMessage(msg); + } + } + CGUIDialog::Process(currentTime, dirtyregions); +} + +void CGUIDialogSubtitles::FillServices() +{ + ClearServices(); + + VECADDONS addons; + CServiceBroker::GetAddonMgr().GetAddons(addons, AddonType::SUBTITLE_MODULE); + + if (addons.empty()) + { + UpdateStatus(NO_SERVICES); + return; + } + + std::string defaultService; + const CFileItem &item = g_application.CurrentUnstackedItem(); + if (item.GetVideoContentType() == VideoDbContentType::TVSHOWS || + item.GetVideoContentType() == VideoDbContentType::EPISODES) + // Set default service for tv shows + defaultService = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SUBTITLES_TV); + else + // Set default service for filemode and movies + defaultService = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SUBTITLES_MOVIE); + + std::string service = addons.front()->ID(); + for (VECADDONS::const_iterator addonIt = addons.begin(); addonIt != addons.end(); ++addonIt) + { + CFileItemPtr item(CAddonsDirectory::FileItemFromAddon(*addonIt, "plugin://" + (*addonIt)->ID(), false)); + m_serviceItems->Add(item); + if ((*addonIt)->ID() == defaultService) + service = (*addonIt)->ID(); + } + + // Bind our services to the UI + CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), CONTROL_SERVICELIST, 0, 0, m_serviceItems); + OnMessage(msg); + + SetService(service); +} + +bool CGUIDialogSubtitles::SetService(const std::string &service) +{ + if (service != m_currentService) + { + m_currentService = service; + CLog::Log(LOGDEBUG, "New Service [{}] ", m_currentService); + + CFileItemPtr currentService = GetService(); + // highlight this item in the skin + for (int i = 0; i < m_serviceItems->Size(); i++) + { + CFileItemPtr pItem = m_serviceItems->Get(i); + pItem->Select(pItem == currentService); + } + + SET_CONTROL_LABEL(CONTROL_NAMELABEL, currentService->GetLabel()); + + if (currentService->HasAddonInfo()) + { + std::string icon = URIUtils::AddFileToFolder(currentService->GetAddonInfo()->Path(), "logo.png"); + SET_CONTROL_FILENAME(CONTROL_NAMELOGO, icon); + } + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->GetSubtitleCount() == 0) + SET_CONTROL_HIDDEN(CONTROL_SUBSEXIST); + else + SET_CONTROL_VISIBLE(CONTROL_SUBSEXIST); + + return true; + } + return false; +} + +const CFileItemPtr CGUIDialogSubtitles::GetService() const +{ + for (int i = 0; i < m_serviceItems->Size(); i++) + { + if (m_serviceItems->Get(i)->GetProperty("Addon.ID") == m_currentService) + return m_serviceItems->Get(i); + } + return CFileItemPtr(); +} + +void CGUIDialogSubtitles::Search(const std::string &search/*=""*/) +{ + if (m_currentService.empty()) + return; // no services available + + UpdateStatus(SEARCHING); + ClearSubtitles(); + + CURL url("plugin://" + m_currentService + "/"); + if (!search.empty()) + { + url.SetOption("action", "manualsearch"); + url.SetOption("searchstring", search); + } + else + url.SetOption("action", "search"); + + const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + SettingConstPtr setting = settings->GetSetting(CSettings::SETTING_SUBTITLES_LANGUAGES); + if (setting) + url.SetOption("languages", setting->ToString()); + + // Check for stacking + if (g_application.CurrentFileItem().IsStack()) + url.SetOption("stack", "1"); + + std::string preferredLanguage = settings->GetString(CSettings::SETTING_LOCALE_SUBTITLELANGUAGE); + + if (StringUtils::EqualsNoCase(preferredLanguage, "original")) + { + AudioStreamInfo info; + std::string strLanguage; + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + appPlayer->GetAudioStreamInfo(CURRENT_STREAM, info); + + if (!g_LangCodeExpander.Lookup(info.language, strLanguage)) + strLanguage = "Unknown"; + + preferredLanguage = strLanguage; + } + else if (StringUtils::EqualsNoCase(preferredLanguage, "default")) + preferredLanguage = g_langInfo.GetEnglishLanguageName(); + + url.SetOption("preferredlanguage", preferredLanguage); + + AddJob(new CSubtitlesJob(url, "")); +} + +void CGUIDialogSubtitles::OnJobComplete(unsigned int jobID, bool success, CJob *job) +{ + const CURL &url = static_cast<CSubtitlesJob*>(job)->GetURL(); + const CFileItemList *items = static_cast<CSubtitlesJob*>(job)->GetItems(); + const std::string &language = static_cast<CSubtitlesJob*>(job)->GetLanguage(); + if (url.GetOption("action") == "search" || url.GetOption("action") == "manualsearch") + OnSearchComplete(items); + else + OnDownloadComplete(items, language); + CJobQueue::OnJobComplete(jobID, success, job); +} + +void CGUIDialogSubtitles::OnSearchComplete(const CFileItemList *items) +{ + std::unique_lock<CCriticalSection> lock(m_critsection); + m_subtitles->Assign(*items); + UpdateStatus(SEARCH_COMPLETE); + m_updateSubsList = true; + MarkDirtyRegion(); + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!items->IsEmpty() && appPlayer->GetSubtitleCount() == 0 && + m_LastAutoDownloaded != g_application.CurrentFile() && + CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_SUBTITLES_DOWNLOADFIRST)) + { + CFileItemPtr item = items->Get(0); + CLog::Log(LOGDEBUG, "{} - Automatically download first subtitle: {}", __FUNCTION__, + item->GetLabel2()); + m_LastAutoDownloaded = g_application.CurrentFile(); + Download(*item); + } + + SetInvalid(); +} + +void CGUIDialogSubtitles::OnSubtitleServiceContextMenu(int itemIdx) +{ + const auto service = m_serviceItems->Get(itemIdx); + + CContextButtons buttons; + // Subtitle addon settings + buttons.Add(static_cast<int>(SUBTITLE_SERVICE_CONTEXT_BUTTONS::ADDON_SETTINGS), + g_localizeStrings.Get(21417)); + // Disable addon + buttons.Add(static_cast<int>(SUBTITLE_SERVICE_CONTEXT_BUTTONS::ADDON_DISABLE), + g_localizeStrings.Get(24021)); + + auto idx = static_cast<SUBTITLE_SERVICE_CONTEXT_BUTTONS>(CGUIDialogContextMenu::Show(buttons)); + switch (idx) + { + case SUBTITLE_SERVICE_CONTEXT_BUTTONS::ADDON_SETTINGS: + { + AddonPtr addon; + if (CServiceBroker::GetAddonMgr().GetAddon(service->GetProperty("Addon.ID").asString(), addon, + AddonType::SUBTITLE_MODULE, + OnlyEnabled::CHOICE_YES)) + { + CGUIDialogAddonSettings::ShowForAddon(addon); + } + else + { + CLog::Log(LOGERROR, "{} - Could not open settings for addon: {}", __FUNCTION__, + service->GetProperty("Addon.ID").asString()); + } + break; + } + case SUBTITLE_SERVICE_CONTEXT_BUTTONS::ADDON_DISABLE: + { + CServiceBroker::GetAddonMgr().DisableAddon(service->GetProperty("Addon.ID").asString(), + AddonDisabledReason::USER); + const bool currentActiveServiceWasDisabled = + m_currentService == service->GetProperty("Addon.ID").asString(); + FillServices(); + // restart search if the current active service was disabled + if (currentActiveServiceWasDisabled && !m_serviceItems->IsEmpty()) + { + Search(); + } + // if no more services are available make sure the subtitle list is cleaned up + else if (m_serviceItems->IsEmpty()) + { + ClearSubtitles(); + } + break; + } + default: + break; + } +} + +void CGUIDialogSubtitles::UpdateStatus(STATUS status) +{ + std::unique_lock<CCriticalSection> lock(m_critsection); + std::string label; + switch (status) + { + case NO_SERVICES: + label = g_localizeStrings.Get(24114); + break; + case SEARCHING: + label = g_localizeStrings.Get(24107); + break; + case SEARCH_COMPLETE: + if (!m_subtitles->IsEmpty()) + label = StringUtils::Format(g_localizeStrings.Get(24108), m_subtitles->Size()); + else + label = g_localizeStrings.Get(24109); + break; + case DOWNLOADING: + label = g_localizeStrings.Get(24110); + break; + default: + break; + } + if (label != m_status) + { + m_status = label; + SetInvalid(); + } +} + +void CGUIDialogSubtitles::Download(const CFileItem &subtitle) +{ + UpdateStatus(DOWNLOADING); + + // subtitle URL should be of the form plugin://<addonid>/?param=foo¶m=bar + // we just append (if not already present) the action=download parameter. + CURL url(subtitle.GetURL()); + if (url.GetOption("action").empty()) + url.SetOption("action", "download"); + + AddJob(new CSubtitlesJob(url, subtitle.GetLabel())); +} + +void CGUIDialogSubtitles::OnDownloadComplete(const CFileItemList *items, const std::string &language) +{ + if (items->IsEmpty()) + { + CFileItemPtr service = GetService(); + if (service) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, service->GetLabel(), g_localizeStrings.Get(24113)); + UpdateStatus(SEARCH_COMPLETE); + return; + } + + SUBTITLE_STORAGEMODE storageMode = (SUBTITLE_STORAGEMODE) CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_SUBTITLES_STORAGEMODE); + + // Get (unstacked) path + std::string strCurrentFile = g_application.CurrentUnstackedItem().GetDynPath(); + + std::string strDownloadPath = "special://temp"; + std::string strDestPath; + std::vector<std::string> vecFiles; + + std::string strCurrentFilePath; + if (StringUtils::StartsWith(strCurrentFilePath, "http://")) + { + strCurrentFile = "TempSubtitle"; + vecFiles.push_back(strCurrentFile); + } + else + { + std::string subPath = CSpecialProtocol::TranslatePath("special://subtitles"); + if (!subPath.empty()) + strDownloadPath = subPath; + + /** Get item's folder for sub storage, special case for RAR/ZIP items + * @todo We need some way to avoid special casing this all over the place + * for rar/zip (perhaps modify GetDirectory?) + */ + if (URIUtils::IsInRAR(strCurrentFile) || URIUtils::IsInZIP(strCurrentFile)) + strCurrentFilePath = URIUtils::GetDirectory(CURL(strCurrentFile).GetHostName()); + else + strCurrentFilePath = URIUtils::GetDirectory(strCurrentFile); + + // Handle stacks + if (g_application.CurrentFileItem().IsStack() && items->Size() > 1) + { + CStackDirectory::GetPaths(g_application.CurrentFileItem().GetPath(), vecFiles); + // Make sure (stack) size is the same as the size of the items handed to us, else fallback to single item + if (items->Size() != (int) vecFiles.size()) + { + vecFiles.clear(); + vecFiles.push_back(strCurrentFile); + } + } + else + { + vecFiles.push_back(strCurrentFile); + } + + if (storageMode == SUBTITLE_STORAGEMODE_MOVIEPATH && + CUtil::SupportsWriteFileOperations(strCurrentFilePath)) + { + strDestPath = strCurrentFilePath; + } + } + + // Use fallback? + if (strDestPath.empty()) + strDestPath = strDownloadPath; + + // Extract the language and appropriate extension + std::string strSubLang; + g_LangCodeExpander.ConvertToISO6391(language, strSubLang); + + // Iterate over all items to transfer + for (unsigned int i = 0; i < vecFiles.size() && i < (unsigned int) items->Size(); i++) + { + std::string strUrl = items->Get(i)->GetPath(); + std::string strFileName = URIUtils::GetFileName(vecFiles[i]); + URIUtils::RemoveExtension(strFileName); + + // construct subtitle path + std::string strSubExt = URIUtils::GetExtension(strUrl); + std::string strSubName = StringUtils::Format("{}.{}{}", strFileName, strSubLang, strSubExt); + + // Handle URL encoding: + std::string strDownloadFile = URIUtils::ChangeBasePath(strCurrentFilePath, strSubName, strDownloadPath); + std::string strDestFile = strDownloadFile; + + if (!CFile::Copy(strUrl, strDownloadFile)) + { + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, strSubName, g_localizeStrings.Get(24113)); + CLog::Log(LOGERROR, "{} - Saving of subtitle {} to {} failed", __FUNCTION__, strUrl, + strDownloadFile); + } + else + { + if (strDestPath != strDownloadPath) + { + // Handle URL encoding: + std::string strTryDestFile = URIUtils::ChangeBasePath(strCurrentFilePath, strSubName, strDestPath); + + /* Copy the file from temp to our final destination, if that fails fallback to download path + * (ie. special://subtitles or use special://temp). Note that after the first item strDownloadPath equals strDestpath + * so that all remaining items (including the .idx below) are copied directly to their final destination and thus all + * items end up in the same folder + */ + CLog::Log(LOGDEBUG, "{} - Saving subtitle {} to {}", __FUNCTION__, strDownloadFile, + strTryDestFile); + if (CFile::Copy(strDownloadFile, strTryDestFile)) + { + CFile::Delete(strDownloadFile); + strDestFile = strTryDestFile; + strDownloadPath = strDestPath; // Update download path so all the other items get directly downloaded to our final destination + } + else + { + CLog::Log(LOGWARNING, "{} - Saving of subtitle {} to {} failed. Falling back to {}", + __FUNCTION__, strDownloadFile, strTryDestFile, strDownloadPath); + strDestPath = strDownloadPath; // Copy failed, use fallback for the rest of the items + } + } + else + { + CLog::Log(LOGDEBUG, "{} - Saved subtitle {} to {}", __FUNCTION__, strUrl, strDownloadFile); + } + + // for ".sub" subtitles we check if ".idx" counterpart exists and copy that as well + if (StringUtils::EqualsNoCase(strSubExt, ".sub")) + { + strUrl = URIUtils::ReplaceExtension(strUrl, ".idx"); + if(CFile::Exists(strUrl)) + { + std::string strSubNameIdx = StringUtils::Format("{}.{}.idx", strFileName, strSubLang); + // Handle URL encoding: + strDestFile = URIUtils::ChangeBasePath(strCurrentFilePath, strSubNameIdx, strDestPath); + CFile::Copy(strUrl, strDestFile); + } + } + + // Set sub for currently playing (stack) item + if (vecFiles[i] == strCurrentFile) + SetSubtitles(strDestFile); + } + } + + // Notify window manager that a subtitle was downloaded + CGUIMessage msg(GUI_MSG_SUBTITLE_DOWNLOADED, 0, 0); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); + + // Close the window + Close(); +} + +void CGUIDialogSubtitles::ClearSubtitles() +{ + CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_SUBLIST); + OnMessage(msg); + std::unique_lock<CCriticalSection> lock(m_critsection); + m_subtitles->Clear(); +} + +void CGUIDialogSubtitles::ClearServices() +{ + CGUIMessage msg(GUI_MSG_LABEL_RESET, GetID(), CONTROL_SERVICELIST); + OnMessage(msg); + m_serviceItems->Clear(); + m_currentService.clear(); +} + +void CGUIDialogSubtitles::SetSubtitles(const std::string &subtitle) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + appPlayer->AddSubtitle(subtitle); +} diff --git a/xbmc/video/dialogs/GUIDialogSubtitles.h b/xbmc/video/dialogs/GUIDialogSubtitles.h new file mode 100644 index 0000000..043a613 --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogSubtitles.h @@ -0,0 +1,72 @@ +/* + * 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. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "threads/CriticalSection.h" +#include "utils/JobManager.h" + +#include <string> + +enum SUBTITLE_STORAGEMODE +{ + SUBTITLE_STORAGEMODE_MOVIEPATH = 0, + SUBTITLE_STORAGEMODE_CUSTOMPATH +}; + +class CFileItem; +class CFileItemList; + +class CGUIDialogSubtitles : public CGUIDialog, CJobQueue +{ +public: + CGUIDialogSubtitles(void); + ~CGUIDialogSubtitles(void) override; + bool OnMessage(CGUIMessage& message) override; + void OnInitWindow() override; + +protected: + void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override; + void OnJobComplete(unsigned int jobID, bool success, CJob *job) override; + + bool SetService(const std::string &service); + const CFileItemPtr GetService() const; + void FillServices(); + void ClearServices(); + void ClearSubtitles(); + + enum STATUS { NO_SERVICES = 0, SEARCHING, SEARCH_COMPLETE, DOWNLOADING }; + void UpdateStatus(STATUS status); + + void Search(const std::string &search=""); + void OnSearchComplete(const CFileItemList *items); + + void Download(const CFileItem &subtitle); + void OnDownloadComplete(const CFileItemList *items, const std::string &language); + + + /*! + \brief Called when the context menu is requested on a subtitle service + present on the list of installed subtitle addons + \param itemIdx the index of the selected subtitle service on the list + */ + void OnSubtitleServiceContextMenu(int itemIdx); + + void SetSubtitles(const std::string &subtitle); + + CCriticalSection m_critsection; + CFileItemList* m_subtitles; + CFileItemList* m_serviceItems; + std::string m_currentService; + std::string m_status; + std::string m_strManualSearch; + bool m_pausedOnRun = false; + bool m_updateSubsList = false; ///< true if we need to update our subs list + std::string m_LastAutoDownloaded; ///< Last video file path which automatically downloaded subtitle +}; diff --git a/xbmc/video/dialogs/GUIDialogTeletext.cpp b/xbmc/video/dialogs/GUIDialogTeletext.cpp new file mode 100644 index 0000000..01e86cb --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogTeletext.cpp @@ -0,0 +1,197 @@ +/* + * 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 "GUIDialogTeletext.h" + +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUITexture.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/Texture.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/ColorUtils.h" +#include "utils/log.h" + +static int teletextFadeAmount = 0; + +CGUIDialogTeletext::CGUIDialogTeletext() + : CGUIDialog(WINDOW_DIALOG_OSD_TELETEXT, ""), m_pTxtTexture(nullptr) +{ + m_renderOrder = RENDER_ORDER_DIALOG_TELETEXT; +} + +CGUIDialogTeletext::~CGUIDialogTeletext() = default; + +bool CGUIDialogTeletext::OnAction(const CAction& action) +{ + if (m_TextDecoder.HandleAction(action)) + { + MarkDirtyRegion(); + return true; + } + + return CGUIDialog::OnAction(action); +} + +bool CGUIDialogTeletext::OnBack(int actionID) +{ + m_bClose = true; + MarkDirtyRegion(); + return true; +} + +bool CGUIDialogTeletext::OnMessage(CGUIMessage& message) +{ + if (message.GetMessage() == GUI_MSG_WINDOW_INIT) + { + /* Do not open if no teletext is available */ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer->HasTeletextCache()) + { + Close(); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(23049), "", 1500, false); + return true; + } + } + else if (message.GetMessage() == GUI_MSG_NOTIFY_ALL) + { + if (message.GetParam1() == GUI_MSG_WINDOW_RESIZE) + { + SetCoordinates(); + } + } + return CGUIDialog::OnMessage(message); +} + +void CGUIDialogTeletext::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) +{ + CGUIDialog::Process(currentTime, dirtyregions); + m_renderRegion = m_vertCoords; +} + +void CGUIDialogTeletext::Render() +{ + // Do not render if we have no texture + if (!m_pTxtTexture) + { + CLog::Log(LOGERROR, "CGUITeletextBox::Render called without texture"); + return; + } + + m_TextDecoder.RenderPage(); + + if (!m_bClose) + { + if (teletextFadeAmount < 100) + { + teletextFadeAmount = std::min(100, teletextFadeAmount + 5); + MarkDirtyRegion(); + } + } + else + { + if (teletextFadeAmount > 0) + { + teletextFadeAmount = std::max(0, teletextFadeAmount - 10); + MarkDirtyRegion(); + } + + if (teletextFadeAmount == 0) + Close(); + } + + unsigned char* textureBuffer = (unsigned char*)m_TextDecoder.GetTextureBuffer(); + if (!m_bClose && m_TextDecoder.NeedRendering() && textureBuffer) + { + m_pTxtTexture->Update(m_TextDecoder.GetWidth(), m_TextDecoder.GetHeight(), m_TextDecoder.GetWidth()*4, XB_FMT_A8R8G8B8, textureBuffer, false); + m_TextDecoder.RenderingDone(); + MarkDirtyRegion(); + } + + UTILS::COLOR::Color color = + (static_cast<UTILS::COLOR::Color>(teletextFadeAmount * 2.55f) & 0xff) << 24 | 0xFFFFFF; + CGUITexture::DrawQuad(m_vertCoords, color, m_pTxtTexture.get()); + + CGUIDialog::Render(); +} + +void CGUIDialogTeletext::OnInitWindow() +{ + teletextFadeAmount = 0; + m_bClose = false; + m_windowLoaded = true; + + SetCoordinates(); + + if (!m_TextDecoder.InitDecoder()) + { + CLog::Log(LOGERROR, "{}: failed to init teletext decoder", __FUNCTION__); + Close(); + } + + m_pTxtTexture = + CTexture::CreateTexture(m_TextDecoder.GetWidth(), m_TextDecoder.GetHeight(), XB_FMT_A8R8G8B8); + if (!m_pTxtTexture) + { + CLog::Log(LOGERROR, "{}: failed to create texture", __FUNCTION__); + Close(); + } + + CGUIDialog::OnInitWindow(); +} + +void CGUIDialogTeletext::OnDeinitWindow(int nextWindowID) +{ + m_windowLoaded = false; + m_TextDecoder.EndDecoder(); + + m_pTxtTexture.reset(); + + CGUIDialog::OnDeinitWindow(nextWindowID); +} + +void CGUIDialogTeletext::SetCoordinates() +{ + float left, right, top, bottom; + + CServiceBroker::GetWinSystem()->GetGfxContext().SetScalingResolution(m_coordsRes, m_needsScaling); + + left = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord(0, 0); + right = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalXCoord((float)m_coordsRes.iWidth, 0); + top = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(0, 0); + bottom = CServiceBroker::GetWinSystem()->GetGfxContext().ScaleFinalYCoord(0, (float)m_coordsRes.iHeight); + + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_VIDEOPLAYER_TELETEXTSCALE)) + { + /* Fixed aspect ratio to 4:3 for teletext */ + float width = right - left; + float height = bottom - top; + if (width / 4 > height / 3) + { + left = (width - height * 4 / 3) / 2; + right = width - left; + } + else + { + top = (height - width * 3 / 4) / 2; + bottom = height - top; + } + } + + m_vertCoords.SetRect(left, + top, + right, + bottom); + + MarkDirtyRegion(); +} diff --git a/xbmc/video/dialogs/GUIDialogTeletext.h b/xbmc/video/dialogs/GUIDialogTeletext.h new file mode 100644 index 0000000..4a60ec0 --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogTeletext.h @@ -0,0 +1,39 @@ +/* + * 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. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "video/Teletext.h" + +#include <memory> + +class CTexture; + +class CGUIDialogTeletext : public CGUIDialog +{ +public: + CGUIDialogTeletext(void); + ~CGUIDialogTeletext(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction& action) override; + bool OnBack(int actionID) override; + void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override; + void Render() override; + void OnInitWindow() override; + void OnDeinitWindow(int nextWindowID) override; + +protected: + bool m_bClose; /* Close sendet, needed for fade out */ + std::unique_ptr<CTexture> m_pTxtTexture; /* Texture info class to render to screen */ + CRect m_vertCoords; /* Coordinates of teletext field on screen */ + CTeletextDecoder m_TextDecoder; /* Decoding class for teletext code */ + +private: + void SetCoordinates(); +}; diff --git a/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp b/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp new file mode 100644 index 0000000..f2669d3 --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp @@ -0,0 +1,590 @@ +/* + * 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 "GUIDialogVideoBookmarks.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "Util.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "messaging/ApplicationMessenger.h" +#include "pictures/Picture.h" +#include "profiles/ProfileManager.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/Crc32.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/VideoThumbLoader.h" +#include "view/ViewState.h" + +#include <mutex> +#include <string> +#include <vector> + +#define BOOKMARK_THUMB_WIDTH CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes + +#define CONTROL_ADD_BOOKMARK 2 +#define CONTROL_CLEAR_BOOKMARKS 3 +#define CONTROL_ADD_EPISODE_BOOKMARK 4 + +#define CONTROL_THUMBS 11 + +CGUIDialogVideoBookmarks::CGUIDialogVideoBookmarks() + : CGUIDialog(WINDOW_DIALOG_VIDEO_BOOKMARKS, "VideoOSDBookmarks.xml"), + CJobQueue(false, 1, CJob::PRIORITY_NORMAL) +{ + m_vecItems = new CFileItemList; + m_loadType = LOAD_EVERY_TIME; + m_jobsStarted = 0; +} + +CGUIDialogVideoBookmarks::~CGUIDialogVideoBookmarks() +{ + delete m_vecItems; +} + +bool CGUIDialogVideoBookmarks::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_WINDOW_DEINIT: + { + CUtil::DeleteVideoDatabaseDirectoryCache(); + Clear(); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + // don't init this dialog if we don't playback a file + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer->IsPlaying()) + return false; + + CGUIWindow::OnMessage(message); + Update(); + return true; + } + break; + + case GUI_MSG_CLICKED: + { + int iControl = message.GetSenderId(); + if (iControl == CONTROL_ADD_BOOKMARK) + { + AddBookmark(); + Update(); + } + else if (iControl == CONTROL_CLEAR_BOOKMARKS) + { + ClearBookmarks(); + } + else if (iControl == CONTROL_ADD_EPISODE_BOOKMARK) + { + AddEpisodeBookmark(); + Update(); + } + else if (m_viewControl.HasControl(iControl)) // list/thumb control + { + int iItem = m_viewControl.GetSelectedItem(); + int iAction = message.GetParam1(); + if (iAction == ACTION_DELETE_ITEM) + { + Delete(iItem); + } + else if (iAction == ACTION_SELECT_ITEM || iAction == ACTION_MOUSE_LEFT_CLICK) + { + GotoBookmark(iItem); + } + } + } + break; + case GUI_MSG_SETFOCUS: + { + if (m_viewControl.HasControl(message.GetControlId()) && m_viewControl.GetCurrentControl() != message.GetControlId()) + { + m_viewControl.SetFocused(); + return true; + } + } + break; + case GUI_MSG_REFRESH_LIST: + { + switch (message.GetParam1()) + { + case 0: + OnRefreshList(); + break; + case 1: + UpdateItem(message.GetParam2()); + break; + default: + break; + } + } + break; + } + + return CGUIDialog::OnMessage(message); +} + +bool CGUIDialogVideoBookmarks::OnAction(const CAction &action) +{ + switch(action.GetID()) + { + case ACTION_CONTEXT_MENU: + case ACTION_MOUSE_RIGHT_CLICK: + { + OnPopupMenu(m_viewControl.GetSelectedItem()); + return true; + } + } + return CGUIDialog::OnAction(action); +} + + +void CGUIDialogVideoBookmarks::OnPopupMenu(int item) +{ + if (item < 0 || item >= (int) m_bookmarks.size()) + return; + + // highlight the item + (*m_vecItems)[item]->Select(true); + + CContextButtons choices; + choices.Add(1, (m_bookmarks[item].type == CBookmark::EPISODE ? 20405 : 20404)); // "Remove episode bookmark" or "Remove bookmark" + + int button = CGUIDialogContextMenu::ShowAndGetChoice(choices); + + // unhighlight the item + (*m_vecItems)[item]->Select(false); + + if (button == 1) + Delete(item); +} + +void CGUIDialogVideoBookmarks::Delete(int item) +{ + if ( item>=0 && (unsigned)item < m_bookmarks.size() ) + { + CVideoDatabase videoDatabase; + videoDatabase.Open(); + std::string path(g_application.CurrentFile()); + if (g_application.CurrentFileItem().HasProperty("original_listitem_url") && + !URIUtils::IsVideoDb(g_application.CurrentFileItem().GetProperty("original_listitem_url").asString())) + path = g_application.CurrentFileItem().GetProperty("original_listitem_url").asString(); + videoDatabase.ClearBookMarkOfFile(path, m_bookmarks[item], m_bookmarks[item].type); + videoDatabase.Close(); + CUtil::DeleteVideoDatabaseDirectoryCache(); + } + Update(); +} + +void CGUIDialogVideoBookmarks::UpdateItem(unsigned int chapterIdx) +{ + std::unique_lock<CCriticalSection> lock(m_refreshSection); + + int itemPos = 0; + for (const auto& item : *m_vecItems) + { + if (chapterIdx == item->GetProperty("chapter").asInteger()) + break; + itemPos++; + } + + if (itemPos < m_vecItems->Size()) + { + std::string time = StringUtils::Format("chapter://{}/{}", m_filePath, chapterIdx); + std::string cachefile = CServiceBroker::GetTextureCache()->GetCachedPath( + CServiceBroker::GetTextureCache()->GetCacheFile(time) + ".jpg"); + if (CFileUtils::Exists(cachefile)) + { + (*m_vecItems)[itemPos]->SetArt("thumb", cachefile); + } + } +} + +void CGUIDialogVideoBookmarks::OnRefreshList() +{ + m_bookmarks.clear(); + std::vector<CFileItemPtr> items; + + // open the d/b and retrieve the bookmarks for the current movie + m_filePath = g_application.CurrentFile(); + if (g_application.CurrentFileItem().HasProperty("original_listitem_url") && + !URIUtils::IsVideoDb(g_application.CurrentFileItem().GetProperty("original_listitem_url").asString())) + m_filePath = g_application.CurrentFileItem().GetProperty("original_listitem_url").asString(); + + CVideoDatabase videoDatabase; + videoDatabase.Open(); + videoDatabase.GetBookMarksForFile(m_filePath, m_bookmarks); + videoDatabase.GetBookMarksForFile(m_filePath, m_bookmarks, CBookmark::EPISODE, true); + videoDatabase.Close(); + + std::unique_lock<CCriticalSection> lock(m_refreshSection); + m_vecItems->Clear(); + + // cycle through each stored bookmark and add it to our list control + for (unsigned int i = 0; i < m_bookmarks.size(); ++i) + { + std::string bookmarkTime; + if (m_bookmarks[i].type == CBookmark::EPISODE) + bookmarkTime = StringUtils::Format("{} {} {} {}", g_localizeStrings.Get(20373), + m_bookmarks[i].seasonNumber, g_localizeStrings.Get(20359), + m_bookmarks[i].episodeNumber); + else + bookmarkTime = StringUtils::SecondsToTimeString((long)m_bookmarks[i].timeInSeconds, TIME_FORMAT_HH_MM_SS); + + CFileItemPtr item(new CFileItem(StringUtils::Format(g_localizeStrings.Get(299), i + 1))); + item->SetLabel2(bookmarkTime); + item->SetArt("thumb", m_bookmarks[i].thumbNailImage); + item->SetProperty("resumepoint", m_bookmarks[i].timeInSeconds); + item->SetProperty("playerstate", m_bookmarks[i].playerState); + item->SetProperty("isbookmark", "true"); + items.push_back(item); + } + + // add chapters if around + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + for (int i = 1; i <= appPlayer->GetChapterCount(); ++i) + { + std::string chapterName; + appPlayer->GetChapterName(chapterName, i); + + int64_t pos = appPlayer->GetChapterPos(i); + std::string time = StringUtils::SecondsToTimeString((long) pos, TIME_FORMAT_HH_MM_SS); + + if (chapterName.empty() || + StringUtils::StartsWithNoCase(chapterName, time) || + StringUtils::IsNaturalNumber(chapterName)) + chapterName = StringUtils::Format(g_localizeStrings.Get(25010), i); + + CFileItemPtr item(new CFileItem(chapterName)); + item->SetLabel2(time); + + std::string chapterPath = StringUtils::Format("chapter://{}/{}", m_filePath, i); + std::string cachefile = CServiceBroker::GetTextureCache()->GetCachedPath( + CServiceBroker::GetTextureCache()->GetCacheFile(chapterPath) + ".jpg"); + if (CFileUtils::Exists(cachefile)) + item->SetArt("thumb", cachefile); + else if (i > m_jobsStarted && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS)) + { + CFileItem item(m_filePath, false); + CJob* job = new CThumbExtractor(item, m_filePath, true, chapterPath, pos * 1000, false); + AddJob(job); + m_mapJobsChapter[job] = i; + m_jobsStarted++; + } + + item->SetProperty("chapter", i); + item->SetProperty("resumepoint", static_cast<double>(pos)); + item->SetProperty("ischapter", "true"); + items.push_back(item); + } + + // sort items by resume point + std::sort(items.begin(), items.end(), [](const CFileItemPtr &item1, const CFileItemPtr &item2) { + return item1->GetProperty("resumepoint").asDouble() < item2->GetProperty("resumepoint").asDouble(); + }); + + // add items to file list and mark the proper item as selected if the current playtime is above + int selectedItemIndex = 0; + double playTime = g_application.GetTime(); + for (auto& item : items) + { + m_vecItems->Add(item); + if (playTime >= item->GetProperty("resumepoint").asDouble()) + selectedItemIndex = m_vecItems->Size() - 1; + } + + m_viewControl.SetItems(*m_vecItems); + m_viewControl.SetSelectedItem(selectedItemIndex); +} + +void CGUIDialogVideoBookmarks::Update() +{ + CVideoDatabase videoDatabase; + videoDatabase.Open(); + + if (g_application.CurrentFileItem().HasVideoInfoTag() && g_application.CurrentFileItem().GetVideoInfoTag()->m_iEpisode > -1) + { + std::vector<CVideoInfoTag> episodes; + videoDatabase.GetEpisodesByFile(g_application.CurrentFile(),episodes); + if (episodes.size() > 1) + { + CONTROL_ENABLE(CONTROL_ADD_EPISODE_BOOKMARK); + } + else + { + CONTROL_DISABLE(CONTROL_ADD_EPISODE_BOOKMARK); + } + } + else + { + CONTROL_DISABLE(CONTROL_ADD_EPISODE_BOOKMARK); + } + + + m_viewControl.SetCurrentView(DEFAULT_VIEW_ICONS); + + // empty the list ready for population + Clear(); + + OnRefreshList(); + + videoDatabase.Close(); +} + +void CGUIDialogVideoBookmarks::Clear() +{ + m_viewControl.Clear(); + m_vecItems->Clear(); +} + +void CGUIDialogVideoBookmarks::GotoBookmark(int item) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (item < 0 || item >= m_vecItems->Size() || !appPlayer->HasPlayer()) + return; + + CFileItemPtr fileItem = m_vecItems->Get(item); + int chapter = static_cast<int>(fileItem->GetProperty("chapter").asInteger()); + if (chapter <= 0) + { + appPlayer->SetPlayerState(fileItem->GetProperty("playerstate").asString()); + g_application.SeekTime(fileItem->GetProperty("resumepoint").asDouble()); + } + else + appPlayer->SeekChapter(chapter); + + Close(); +} + +void CGUIDialogVideoBookmarks::ClearBookmarks() +{ + CVideoDatabase videoDatabase; + videoDatabase.Open(); + std::string path = g_application.CurrentFile(); + if (g_application.CurrentFileItem().HasProperty("original_listitem_url") && + !URIUtils::IsVideoDb(g_application.CurrentFileItem().GetProperty("original_listitem_url").asString())) + path = g_application.CurrentFileItem().GetProperty("original_listitem_url").asString(); + videoDatabase.ClearBookMarksOfFile(path, CBookmark::STANDARD); + videoDatabase.ClearBookMarksOfFile(path, CBookmark::RESUME); + videoDatabase.ClearBookMarksOfFile(path, CBookmark::EPISODE); + videoDatabase.Close(); + Update(); +} + +bool CGUIDialogVideoBookmarks::AddBookmark(CVideoInfoTag* tag) +{ + CVideoDatabase videoDatabase; + CBookmark bookmark; + bookmark.timeInSeconds = (int)g_application.GetTime(); + bookmark.totalTimeInSeconds = (int)g_application.GetTotalTime(); + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + if (appPlayer->HasPlayer()) + bookmark.playerState = appPlayer->GetPlayerState(); + else + bookmark.playerState.clear(); + + bookmark.player = g_application.GetCurrentPlayer(); + + // create the thumbnail image + float aspectRatio = appPlayer->GetRenderAspectRatio(); + int width = BOOKMARK_THUMB_WIDTH; + int height = (int)(BOOKMARK_THUMB_WIDTH / aspectRatio); + if (height > (int)BOOKMARK_THUMB_WIDTH) + { + height = BOOKMARK_THUMB_WIDTH; + width = (int)(BOOKMARK_THUMB_WIDTH * aspectRatio); + } + + + uint8_t *pixels = (uint8_t*)malloc(height * width * 4); + unsigned int captureId = appPlayer->RenderCaptureAlloc(); + + appPlayer->RenderCapture(captureId, width, height, CAPTUREFLAG_IMMEDIATELY); + bool hasImage = appPlayer->RenderCaptureGetPixels(captureId, 1000, pixels, height * width * 4); + + if (hasImage) + { + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + auto crc = Crc32::ComputeFromLowerCase(g_application.CurrentFile()); + bookmark.thumbNailImage = + StringUtils::Format("{:08x}_{}.jpg", crc, (int)bookmark.timeInSeconds); + bookmark.thumbNailImage = URIUtils::AddFileToFolder(profileManager->GetBookmarksThumbFolder(), bookmark.thumbNailImage); + + if (!CPicture::CreateThumbnailFromSurface(pixels, width, height, width * 4, + bookmark.thumbNailImage)) + { + bookmark.thumbNailImage.clear(); + } + else + CLog::Log(LOGERROR,"CGUIDialogVideoBookmarks: failed to create thumbnail"); + + appPlayer->RenderCaptureRelease(captureId); + } + else + CLog::Log(LOGERROR,"CGUIDialogVideoBookmarks: failed to create thumbnail 2"); + + free(pixels); + + videoDatabase.Open(); + if (tag) + videoDatabase.AddBookMarkForEpisode(*tag, bookmark); + else + { + std::string path = g_application.CurrentFile(); + if (g_application.CurrentFileItem().HasProperty("original_listitem_url") && + !URIUtils::IsVideoDb(g_application.CurrentFileItem().GetProperty("original_listitem_url").asString())) + path = g_application.CurrentFileItem().GetProperty("original_listitem_url").asString(); + videoDatabase.AddBookMarkToFile(path, bookmark, CBookmark::STANDARD); + } + videoDatabase.Close(); + return true; +} + +void CGUIDialogVideoBookmarks::OnWindowLoaded() +{ + CGUIDialog::OnWindowLoaded(); + m_viewControl.Reset(); + m_viewControl.SetParentWindow(GetID()); + m_viewControl.AddView(GetControl(CONTROL_THUMBS)); + m_jobsStarted = 0; + m_mapJobsChapter.clear(); + m_vecItems->Clear(); +} + +void CGUIDialogVideoBookmarks::OnWindowUnload() +{ + //stop running thumb extraction jobs + CancelJobs(); + m_mapJobsChapter.clear(); + m_vecItems->Clear(); + CGUIDialog::OnWindowUnload(); + m_viewControl.Reset(); +} + +CGUIControl *CGUIDialogVideoBookmarks::GetFirstFocusableControl(int id) +{ + if (m_viewControl.HasControl(id)) + id = m_viewControl.GetCurrentControl(); + return CGUIWindow::GetFirstFocusableControl(id); +} + +bool CGUIDialogVideoBookmarks::AddEpisodeBookmark() +{ + std::vector<CVideoInfoTag> episodes; + CVideoDatabase videoDatabase; + videoDatabase.Open(); + videoDatabase.GetEpisodesByFile(g_application.CurrentFile(), episodes); + videoDatabase.Close(); + if (!episodes.empty()) + { + CContextButtons choices; + for (unsigned int i=0; i < episodes.size(); ++i) + { + std::string strButton = + StringUtils::Format("{} {}, {} {}", g_localizeStrings.Get(20373), episodes[i].m_iSeason, + g_localizeStrings.Get(20359), episodes[i].m_iEpisode); + choices.Add(i, strButton); + } + + int pressed = CGUIDialogContextMenu::ShowAndGetChoice(choices); + if (pressed >= 0) + { + AddBookmark(&episodes[pressed]); + return true; + } + } + return false; +} + + + +bool CGUIDialogVideoBookmarks::OnAddBookmark() +{ + if (!g_application.CurrentFileItem().IsVideo()) + return false; + + if (CGUIDialogVideoBookmarks::AddBookmark()) + { + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_REFRESH_LIST, 0, WINDOW_DIALOG_VIDEO_BOOKMARKS); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, + g_localizeStrings.Get(298), // "Bookmarks" + g_localizeStrings.Get(21362));// "Bookmark created" + return true; + } + return false; +} + +bool CGUIDialogVideoBookmarks::OnAddEpisodeBookmark() +{ + bool bReturn = false; + if (g_application.CurrentFileItem().HasVideoInfoTag() && g_application.CurrentFileItem().GetVideoInfoTag()->m_iEpisode > -1) + { + CVideoDatabase videoDatabase; + videoDatabase.Open(); + std::vector<CVideoInfoTag> episodes; + videoDatabase.GetEpisodesByFile(g_application.CurrentFile(),episodes); + if (episodes.size() > 1) + { + bReturn = CGUIDialogVideoBookmarks::AddEpisodeBookmark(); + if(bReturn) + { + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(GUI_MSG_REFRESH_LIST, 0, WINDOW_DIALOG_VIDEO_BOOKMARKS); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, + g_localizeStrings.Get(298), // "Bookmarks" + g_localizeStrings.Get(21363));// "Episode Bookmark created" + + } + } + videoDatabase.Close(); + } + return bReturn; +} + +void CGUIDialogVideoBookmarks::OnJobComplete(unsigned int jobID, + bool success, CJob* job) +{ + if (success && IsActive()) + { + MAPJOBSCHAPS::iterator iter = m_mapJobsChapter.find(job); + if (iter != m_mapJobsChapter.end()) + { + unsigned int chapterIdx = (*iter).second; + CGUIMessage m(GUI_MSG_REFRESH_LIST, GetID(), 0, 1, chapterIdx); + CServiceBroker::GetAppMessenger()->SendGUIMessage(m); + m_mapJobsChapter.erase(iter); + } + } + CJobQueue::OnJobComplete(jobID, success, job); +} diff --git a/xbmc/video/dialogs/GUIDialogVideoBookmarks.h b/xbmc/video/dialogs/GUIDialogVideoBookmarks.h new file mode 100644 index 0000000..801afcd --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogVideoBookmarks.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#pragma once + +#include "guilib/GUIDialog.h" +#include "utils/JobManager.h" +#include "video/VideoDatabase.h" +#include "view/GUIViewControl.h" + +class CFileItemList; + +class CGUIDialogVideoBookmarks : public CGUIDialog, public CJobQueue +{ + typedef std::map<CJob*, unsigned int> MAPJOBSCHAPS; + +public: + CGUIDialogVideoBookmarks(void); + ~CGUIDialogVideoBookmarks(void) override; + bool OnMessage(CGUIMessage& message) override; + void OnWindowLoaded() override; + void OnWindowUnload() override; + bool OnAction(const CAction &action) override; + + /*! + \brief Creates a bookmark of the currently playing video file. + + NOTE: sends a GUI_MSG_REFRESH_LIST message to DialogVideoBookmark on success + \return True if creation of bookmark was successful + \sa OnAddEpisodeBookmark + */ + static bool OnAddBookmark(); + + /*! + \brief Creates an episode bookmark of the currently playing file + + An episode bookmark specifies the end/beginning of episodes on files like: S01E01E02 + Fails if the current video isn't a multi-episode file + NOTE: sends a GUI_MSG_REFRESH_LIST message to DialogVideoBookmark on success + \return True, if bookmark was successfully created + \sa OnAddBookmark + **/ + static bool OnAddEpisodeBookmark(); + + + void Update(); +protected: + void GotoBookmark(int iItem); + void ClearBookmarks(); + static bool AddEpisodeBookmark(); + static bool AddBookmark(CVideoInfoTag *tag=NULL); + void Delete(int item); + void Clear(); + void OnRefreshList(); + void OnPopupMenu(int item); + CGUIControl *GetFirstFocusableControl(int id) override; + + void OnJobComplete(unsigned int jobID, bool success, CJob* job) override; + + CFileItemList* m_vecItems; + CGUIViewControl m_viewControl; + VECBOOKMARKS m_bookmarks; + +private: + void UpdateItem(unsigned int chapterIdx); + + int m_jobsStarted; + std::string m_filePath; + CCriticalSection m_refreshSection; + MAPJOBSCHAPS m_mapJobsChapter; +}; 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 <iterator> +#include <string> + +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<CGUIDialogSelect>(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<CFileItem>(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<CProfileManager> 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<std::string> &artists = m_movieItem->GetVideoInfoTag()->m_artist; + for (std::vector<std::string>::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<CSettings> 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<CSettingList> setting(std::dynamic_pointer_cast<CSettingList>( + 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<CGUIImage*>(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<CGUIDialogProgress>(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<CGUIDialogSelect>(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<CFileItem*>(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<CGUIWindowVideoNav>(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<std::string>& 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<std::string>& artTypes, const CVideoInfoTag& tag, + CVideoDatabase& db) +{ + std::map<std::string, std::string> 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<std::string>& artTypes, const CVideoInfoTag& tag, + CVideoDatabase& db) +{ + std::vector<std::string> 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<std::string>& 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<std::string> GetArtTypesList(const CVideoInfoTag& tag) +{ + CVideoDatabase db; + db.Open(); + + std::vector<std::string> 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<CGUIDialogSelect>(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<std::string> artTypes = GetArtTypesList(*videoItem.GetVideoInfoTag()); + + for (std::vector<std::string>::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<std::string> 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<CGUIDialogSelect>(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<CFileItem>& 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<CONTEXT_BUTTON>(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<CFileItem>& 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<std::string, std::string> 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<CFileItem>& 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<CFileItem>& 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<CGUIDialogYesNo>(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<CFileItem>& 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<CProfileManager> 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<CFileItem>& 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<CGUIDialogSelect>(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<int> 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<CFileItem>& 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<CGUIDialogSelect>(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<std::string, std::string> 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<CGUIDialogSelect>(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<CFileItem>& 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<CFileItem>& 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<CFileItem>& 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<std::string> 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<CFileItem>& 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<CFileItem>& item, + bool bRemove, + CVideoDatabase& database) +{ + int dbId = item->GetVideoInfoTag()->m_iDbId; + + CFileItemList list; + if (bRemove) + { + std::vector<int> 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<int> 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<CGUIDialogSelect>(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<CFileItem>& 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<CGUIWindowVideoNav>(WINDOW_VIDEO_NAV); + if (window) + { + ADDON::ScraperPtr info; + window->OnItemInfo(item, info); + } +} diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.h b/xbmc/video/dialogs/GUIDialogVideoInfo.h new file mode 100644 index 0000000..ac19e98 --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.h @@ -0,0 +1,115 @@ +/* + * 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. + */ + +#pragma once + +#include "MediaSource.h" +#include "guilib/GUIDialog.h" +#include "media/MediaType.h" + +#include <memory> + +class CFileItem; +class CFileItemList; +class CVideoDatabase; + +class CGUIDialogVideoInfo : + public CGUIDialog +{ +public: + CGUIDialogVideoInfo(void); + ~CGUIDialogVideoInfo(void) override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; + void SetMovie(const CFileItem *item); + bool NeedRefresh() const; + bool RefreshAll() const; + bool HasUpdatedThumb() const { return m_hasUpdatedThumb; } + bool HasUpdatedUserrating() const { return m_hasUpdatedUserrating; } + + std::string GetThumbnail() const; + std::shared_ptr<CFileItem> GetCurrentListItem(int offset = 0) override { return m_movieItem; } + const CFileItemList& CurrentDirectory() const { return *m_castList; } + bool HasListItems() const override { return true; } + + static void AddItemPathToFileBrowserSources(VECSOURCES &sources, const CFileItem &item); + + static int ManageVideoItem(const std::shared_ptr<CFileItem>& item); + static bool UpdateVideoItemTitle(const std::shared_ptr<CFileItem>& pItem); + static bool CanDeleteVideoItem(const std::shared_ptr<CFileItem>& item); + static bool DeleteVideoItemFromDatabase(const std::shared_ptr<CFileItem>& item, + bool unavailable = false); + static bool DeleteVideoItem(const std::shared_ptr<CFileItem>& item, bool unavailable = false); + + static bool ManageMovieSets(const std::shared_ptr<CFileItem>& item); + static bool GetMoviesForSet(const CFileItem *setItem, CFileItemList &originalMovies, CFileItemList &selectedMovies); + static bool GetSetForMovie(const CFileItem* movieItem, std::shared_ptr<CFileItem>& selectedSet); + static bool SetMovieSet(const CFileItem *movieItem, const CFileItem *selectedSet); + + static bool GetItemsForTag(const std::string &strHeading, const std::string &type, CFileItemList &items, int idTag = -1, bool showAll = true); + static bool AddItemsToTag(const std::shared_ptr<CFileItem>& tagItem); + static bool RemoveItemsFromTag(const std::shared_ptr<CFileItem>& tagItem); + + static bool ManageVideoItemArtwork(const std::shared_ptr<CFileItem>& item, const MediaType& type); + + static std::string GetLocalizedVideoType(const std::string &strType); + + static void ShowFor(const CFileItem& item); + +protected: + void OnInitWindow() override; + void Update(); + void SetLabel(int iControl, const std::string& strLabel); + void SetUserrating(int userrating) const; + + // link cast to movies + void ClearCastList(); + /** + * \brief Search the current directory for a string got from the virtual keyboard + * \param strSearch The search string + */ + void OnSearch(std::string& strSearch); + /** + * \brief Make the actual search for the OnSearch function. + * \param strSearch The search string + * \param items Items Found + */ + void DoSearch(std::string& strSearch, CFileItemList& items) const; + /** + * \brief React on the selected search item + * \param pItem Search result item + */ + void OnSearchItemFound(const CFileItem* pItem); + void Play(bool resume = false); + void OnGetArt(); + void OnGetFanart(); + void OnSetUserrating() const; + void PlayTrailer(); + + static bool UpdateVideoItemSortTitle(const std::shared_ptr<CFileItem>& pItem); + static bool LinkMovieToTvShow(const std::shared_ptr<CFileItem>& item, + bool bRemove, + CVideoDatabase& database); + + /*! \brief Pop up a fanart chooser. Does not utilise remote URLs. + \param videoItem the item to choose fanart for. + */ + static bool OnGetFanart(const std::shared_ptr<CFileItem>& videoItem); + + std::shared_ptr<CFileItem> m_movieItem; + CFileItemList *m_castList; + bool m_bViewReview = false; + bool m_bRefresh = false; + bool m_bRefreshAll = true; + bool m_hasUpdatedThumb = false; + bool m_hasUpdatedUserrating = false; + int m_startUserrating = -1; + +private: + static std::string ChooseArtType(const CFileItem& item); +}; diff --git a/xbmc/video/dialogs/GUIDialogVideoOSD.cpp b/xbmc/video/dialogs/GUIDialogVideoOSD.cpp new file mode 100644 index 0000000..3b8009d --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogVideoOSD.cpp @@ -0,0 +1,101 @@ +/* + * 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 "GUIDialogVideoOSD.h" + +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "application/Application.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/WindowIDs.h" +#include "input/InputManager.h" +#include "input/actions/ActionIDs.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" + +using namespace PVR; + +CGUIDialogVideoOSD::CGUIDialogVideoOSD(void) + : CGUIDialog(WINDOW_DIALOG_VIDEO_OSD, "VideoOSD.xml") +{ + m_loadType = KEEP_IN_MEMORY; +} + +CGUIDialogVideoOSD::~CGUIDialogVideoOSD(void) = default; + +void CGUIDialogVideoOSD::FrameMove() +{ + if (m_autoClosing) + { + // check for movement of mouse or a submenu open + if (CServiceBroker::GetInputManager().IsMouseActive() + || CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_AUDIO_OSD_SETTINGS) + || CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_SUBTITLE_OSD_SETTINGS) + || CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_VIDEO_OSD_SETTINGS) + || CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_CMS_OSD_SETTINGS) + || CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_VIDEO_BOOKMARKS) + || CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_PVR_OSD_CHANNELS) + || CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_PVR_CHANNEL_GUIDE) + || CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_OSD_TELETEXT)) + // extend show time by original value + SetAutoClose(m_showDuration); + } + CGUIDialog::FrameMove(); +} + +bool CGUIDialogVideoOSD::OnAction(const CAction &action) +{ + if (action.GetID() == ACTION_SHOW_OSD) + { + Close(); + return true; + } + + return CGUIDialog::OnAction(action); +} + +EVENT_RESULT CGUIDialogVideoOSD::OnMouseEvent(const CPoint &point, const CMouseEvent &event) +{ + if (event.m_id == ACTION_MOUSE_WHEEL_UP) + { + return g_application.OnAction(CAction(ACTION_ANALOG_SEEK_FORWARD, 0.5f)) ? EVENT_RESULT_HANDLED : EVENT_RESULT_UNHANDLED; + } + if (event.m_id == ACTION_MOUSE_WHEEL_DOWN) + { + return g_application.OnAction(CAction(ACTION_ANALOG_SEEK_BACK, 0.5f)) ? EVENT_RESULT_HANDLED : EVENT_RESULT_UNHANDLED; + } + + return CGUIDialog::OnMouseEvent(point, event); +} + +bool CGUIDialogVideoOSD::OnMessage(CGUIMessage& message) +{ + switch ( message.GetMessage() ) + { + case GUI_MSG_VIDEO_MENU_STARTED: + { + // We have gone to the DVD menu, so close the OSD. + Close(); + } + break; + case GUI_MSG_WINDOW_DEINIT: // fired when OSD is hidden + { + // Remove our subdialogs if visible + CGUIDialog *pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetDialog(WINDOW_DIALOG_AUDIO_OSD_SETTINGS); + if (pDialog && pDialog->IsDialogRunning()) + pDialog->Close(true); + pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetDialog(WINDOW_DIALOG_SUBTITLE_OSD_SETTINGS); + if (pDialog && pDialog->IsDialogRunning()) + pDialog->Close(true); + } + break; + } + return CGUIDialog::OnMessage(message); +} + diff --git a/xbmc/video/dialogs/GUIDialogVideoOSD.h b/xbmc/video/dialogs/GUIDialogVideoOSD.h new file mode 100644 index 0000000..95cfc9f --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogVideoOSD.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#pragma once + +#include "guilib/GUIDialog.h" + +class CGUIDialogVideoOSD : public CGUIDialog +{ +public: + + CGUIDialogVideoOSD(void); + ~CGUIDialogVideoOSD(void) override; + + void FrameMove() override; + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction &action) override; +protected: + EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override; +}; diff --git a/xbmc/video/dialogs/GUIDialogVideoSettings.cpp b/xbmc/video/dialogs/GUIDialogVideoSettings.cpp new file mode 100644 index 0000000..24d9d22 --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogVideoSettings.cpp @@ -0,0 +1,573 @@ +/* + * 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 "GUIDialogVideoSettings.h" + +#include "GUIPassword.h" +#include "ServiceBroker.h" +#include "addons/Skin.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "dialogs/GUIDialogYesNo.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "profiles/ProfileManager.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingsManager.h" +#include "utils/LangCodeExpander.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/ViewModeSettings.h" + +#include <utility> + +#define SETTING_VIDEO_VIEW_MODE "video.viewmode" +#define SETTING_VIDEO_ZOOM "video.zoom" +#define SETTING_VIDEO_PIXEL_RATIO "video.pixelratio" +#define SETTING_VIDEO_BRIGHTNESS "video.brightness" +#define SETTING_VIDEO_CONTRAST "video.contrast" +#define SETTING_VIDEO_GAMMA "video.gamma" +#define SETTING_VIDEO_NONLIN_STRETCH "video.nonlinearstretch" +#define SETTING_VIDEO_POSTPROCESS "video.postprocess" +#define SETTING_VIDEO_VERTICAL_SHIFT "video.verticalshift" +#define SETTING_VIDEO_TONEMAP_METHOD "video.tonemapmethod" +#define SETTING_VIDEO_TONEMAP_PARAM "video.tonemapparam" +#define SETTING_VIDEO_ORIENTATION "video.orientation" + +#define SETTING_VIDEO_VDPAU_NOISE "vdpau.noise" +#define SETTING_VIDEO_VDPAU_SHARPNESS "vdpau.sharpness" + +#define SETTING_VIDEO_INTERLACEMETHOD "video.interlacemethod" +#define SETTING_VIDEO_SCALINGMETHOD "video.scalingmethod" + +#define SETTING_VIDEO_STEREOSCOPICMODE "video.stereoscopicmode" +#define SETTING_VIDEO_STEREOSCOPICINVERT "video.stereoscopicinvert" + +#define SETTING_VIDEO_MAKE_DEFAULT "video.save" +#define SETTING_VIDEO_CALIBRATION "video.calibration" +#define SETTING_VIDEO_STREAM "video.stream" + +CGUIDialogVideoSettings::CGUIDialogVideoSettings() + : CGUIDialogSettingsManualBase(WINDOW_DIALOG_VIDEO_OSD_SETTINGS, "DialogSettings.xml") +{ } + +CGUIDialogVideoSettings::~CGUIDialogVideoSettings() = default; + +void CGUIDialogVideoSettings::OnSettingChanged(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + const std::string &settingId = setting->GetId(); + if (settingId == SETTING_VIDEO_INTERLACEMETHOD) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_InterlaceMethod = static_cast<EINTERLACEMETHOD>(std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_SCALINGMETHOD) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_ScalingMethod = static_cast<ESCALINGMETHOD>(std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_STREAM) + { + m_videoStream = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + // only change the video stream if a different one has been asked for + if (appPlayer->GetVideoStream() != m_videoStream) + { + appPlayer->SetVideoStream(m_videoStream); // Set the video stream to the one selected + } + } + else if (settingId == SETTING_VIDEO_VIEW_MODE) + { + int value = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + const CVideoSettings vs = appPlayer->GetVideoSettings(); + + appPlayer->SetRenderViewMode(value, vs.m_CustomZoomAmount, vs.m_CustomPixelRatio, + vs.m_CustomVerticalShift, vs.m_CustomNonLinStretch); + + m_viewModeChanged = true; + GetSettingsManager()->SetNumber(SETTING_VIDEO_ZOOM, static_cast<double>(vs.m_CustomZoomAmount)); + GetSettingsManager()->SetNumber(SETTING_VIDEO_PIXEL_RATIO, + static_cast<double>(vs.m_CustomPixelRatio)); + GetSettingsManager()->SetNumber(SETTING_VIDEO_VERTICAL_SHIFT, + static_cast<double>(vs.m_CustomVerticalShift)); + GetSettingsManager()->SetBool(SETTING_VIDEO_NONLIN_STRETCH, vs.m_CustomNonLinStretch); + m_viewModeChanged = false; + } + else if (settingId == SETTING_VIDEO_ZOOM || + settingId == SETTING_VIDEO_VERTICAL_SHIFT || + settingId == SETTING_VIDEO_PIXEL_RATIO || + settingId == SETTING_VIDEO_NONLIN_STRETCH) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + if (settingId == SETTING_VIDEO_ZOOM) + vs.m_CustomZoomAmount = static_cast<float>(std::static_pointer_cast<const CSettingNumber>(setting)->GetValue()); + else if (settingId == SETTING_VIDEO_VERTICAL_SHIFT) + vs.m_CustomVerticalShift = static_cast<float>(std::static_pointer_cast<const CSettingNumber>(setting)->GetValue()); + else if (settingId == SETTING_VIDEO_PIXEL_RATIO) + vs.m_CustomPixelRatio = static_cast<float>(std::static_pointer_cast<const CSettingNumber>(setting)->GetValue()); + else if (settingId == SETTING_VIDEO_NONLIN_STRETCH) + vs.m_CustomNonLinStretch = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + + // try changing the view mode to custom. If it already is set to custom + // manually call the render manager + if (GetSettingsManager()->GetInt(SETTING_VIDEO_VIEW_MODE) != ViewModeCustom) + GetSettingsManager()->SetInt(SETTING_VIDEO_VIEW_MODE, ViewModeCustom); + else + appPlayer->SetRenderViewMode(vs.m_ViewMode, vs.m_CustomZoomAmount, vs.m_CustomPixelRatio, + vs.m_CustomVerticalShift, vs.m_CustomNonLinStretch); + } + else if (settingId == SETTING_VIDEO_POSTPROCESS) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_PostProcess = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_BRIGHTNESS) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_Brightness = static_cast<float>(std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_CONTRAST) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_Contrast = static_cast<float>(std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_GAMMA) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_Gamma = static_cast<float>(std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_VDPAU_NOISE) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_NoiseReduction = static_cast<float>(std::static_pointer_cast<const CSettingNumber>(setting)->GetValue()); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_VDPAU_SHARPNESS) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_Sharpness = static_cast<float>(std::static_pointer_cast<const CSettingNumber>(setting)->GetValue()); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_TONEMAP_METHOD) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_ToneMapMethod = static_cast<ETONEMAPMETHOD>( + std::static_pointer_cast<const CSettingInt>(setting)->GetValue()); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_TONEMAP_PARAM) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_ToneMapParam = static_cast<float>(std::static_pointer_cast<const CSettingNumber>(setting)->GetValue()); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_ORIENTATION) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_Orientation = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_STEREOSCOPICMODE) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_StereoMode = std::static_pointer_cast<const CSettingInt>(setting)->GetValue(); + appPlayer->SetVideoSettings(vs); + } + else if (settingId == SETTING_VIDEO_STEREOSCOPICINVERT) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + vs.m_StereoInvert = std::static_pointer_cast<const CSettingBool>(setting)->GetValue(); + appPlayer->SetVideoSettings(vs); + } +} + +void CGUIDialogVideoSettings::OnSettingAction(const std::shared_ptr<const CSetting>& setting) +{ + if (setting == NULL) + return; + + CGUIDialogSettingsManualBase::OnSettingChanged(setting); + + const std::string &settingId = setting->GetId(); + if (settingId == SETTING_VIDEO_CALIBRATION) + { + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + auto settingsComponent = CServiceBroker::GetSettingsComponent(); + if (!settingsComponent) + return; + + auto settings = settingsComponent->GetSettings(); + if (!settings) + return; + + auto calibsetting = settings->GetSetting(CSettings::SETTING_VIDEOSCREEN_GUICALIBRATION); + if (!calibsetting) + { + CLog::Log(LOGERROR, "Failed to load setting for: {}", + CSettings::SETTING_VIDEOSCREEN_GUICALIBRATION); + return; + } + + // launch calibration window + if (profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && + g_passwordManager.CheckSettingLevelLock(calibsetting->GetLevel())) + return; + + CServiceBroker::GetGUI()->GetWindowManager().ForceActivateWindow(WINDOW_SCREEN_CALIBRATION); + } + //! @todo implement + else if (settingId == SETTING_VIDEO_MAKE_DEFAULT) + Save(); +} + +bool CGUIDialogVideoSettings::Save() +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + if (profileManager->GetMasterProfile().getLockMode() != LOCK_MODE_EVERYONE && + !g_passwordManager.CheckSettingLevelLock(::SettingLevel::Expert)) + return true; + + // prompt user if they are sure + if (CGUIDialogYesNo::ShowAndGetInput(CVariant(12376), CVariant(12377))) + { // reset the settings + CVideoDatabase db; + if (!db.Open()) + return true; + db.EraseAllVideoSettings(); + db.Close(); + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + CMediaSettings::GetInstance().GetDefaultVideoSettings() = appPlayer->GetVideoSettings(); + CMediaSettings::GetInstance().GetDefaultVideoSettings().m_SubtitleStream = -1; + CMediaSettings::GetInstance().GetDefaultVideoSettings().m_AudioStream = -1; + CServiceBroker::GetSettingsComponent()->GetSettings()->Save(); + } + + return true; +} + +void CGUIDialogVideoSettings::SetupView() +{ + CGUIDialogSettingsManualBase::SetupView(); + + SetHeading(13395); + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_OKAY_BUTTON); + SET_CONTROL_HIDDEN(CONTROL_SETTINGS_CUSTOM_BUTTON); + SET_CONTROL_LABEL(CONTROL_SETTINGS_CANCEL_BUTTON, 15067); +} + +void CGUIDialogVideoSettings::InitializeSettings() +{ + CGUIDialogSettingsManualBase::InitializeSettings(); + + const std::shared_ptr<CSettingCategory> category = AddCategory("videosettings", -1); + if (category == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogVideoSettings: unable to setup settings"); + return; + } + + // get all necessary setting groups + const std::shared_ptr<CSettingGroup> groupVideoStream = AddGroup(category); + if (groupVideoStream == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogVideoSettings: unable to setup settings"); + return; + } + const std::shared_ptr<CSettingGroup> groupVideo = AddGroup(category); + if (groupVideo == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogVideoSettings: unable to setup settings"); + return; + } + const std::shared_ptr<CSettingGroup> groupStereoscopic = AddGroup(category); + if (groupStereoscopic == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogVideoSettings: unable to setup settings"); + return; + } + const std::shared_ptr<CSettingGroup> groupSaveAsDefault = AddGroup(category); + if (groupSaveAsDefault == NULL) + { + CLog::Log(LOGERROR, "CGUIDialogVideoSettings: unable to setup settings"); + return; + } + + bool usePopup = g_SkinInfo->HasSkinFile("DialogSlider.xml"); + + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + const CVideoSettings videoSettings = appPlayer->GetVideoSettings(); + + TranslatableIntegerSettingOptions entries; + + entries.clear(); + entries.push_back(TranslatableIntegerSettingOption(16039, VS_INTERLACEMETHOD_NONE)); + entries.push_back(TranslatableIntegerSettingOption(16019, VS_INTERLACEMETHOD_AUTO)); + entries.push_back(TranslatableIntegerSettingOption(20131, VS_INTERLACEMETHOD_RENDER_BLEND)); + entries.push_back(TranslatableIntegerSettingOption(20129, VS_INTERLACEMETHOD_RENDER_WEAVE)); + entries.push_back(TranslatableIntegerSettingOption(16021, VS_INTERLACEMETHOD_RENDER_BOB)); + entries.push_back(TranslatableIntegerSettingOption(16020, VS_INTERLACEMETHOD_DEINTERLACE)); + entries.push_back(TranslatableIntegerSettingOption(16036, VS_INTERLACEMETHOD_DEINTERLACE_HALF)); + entries.push_back( + TranslatableIntegerSettingOption(16311, VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL)); + entries.push_back(TranslatableIntegerSettingOption(16310, VS_INTERLACEMETHOD_VDPAU_TEMPORAL)); + entries.push_back(TranslatableIntegerSettingOption(16325, VS_INTERLACEMETHOD_VDPAU_BOB)); + entries.push_back( + TranslatableIntegerSettingOption(16318, VS_INTERLACEMETHOD_VDPAU_TEMPORAL_SPATIAL_HALF)); + entries.push_back( + TranslatableIntegerSettingOption(16317, VS_INTERLACEMETHOD_VDPAU_TEMPORAL_HALF)); + entries.push_back(TranslatableIntegerSettingOption(16327, VS_INTERLACEMETHOD_VAAPI_BOB)); + entries.push_back(TranslatableIntegerSettingOption(16328, VS_INTERLACEMETHOD_VAAPI_MADI)); + entries.push_back(TranslatableIntegerSettingOption(16329, VS_INTERLACEMETHOD_VAAPI_MACI)); + entries.push_back(TranslatableIntegerSettingOption(16320, VS_INTERLACEMETHOD_DXVA_AUTO)); + + /* remove unsupported methods */ + for (TranslatableIntegerSettingOptions::iterator it = entries.begin(); it != entries.end(); ) + { + if (appPlayer->Supports(static_cast<EINTERLACEMETHOD>(it->value))) + ++it; + else + it = entries.erase(it); + } + + if (!entries.empty()) + { + EINTERLACEMETHOD method = videoSettings.m_InterlaceMethod; + if (!appPlayer->Supports(method)) + { + method = appPlayer->GetDeinterlacingMethodDefault(); + } + AddSpinner(groupVideo, SETTING_VIDEO_INTERLACEMETHOD, 16038, SettingLevel::Basic, static_cast<int>(method), entries); + } + + entries.clear(); + entries.push_back(TranslatableIntegerSettingOption(16301, VS_SCALINGMETHOD_NEAREST)); + entries.push_back(TranslatableIntegerSettingOption(16302, VS_SCALINGMETHOD_LINEAR)); + entries.push_back(TranslatableIntegerSettingOption(16303, VS_SCALINGMETHOD_CUBIC_B_SPLINE)); + entries.push_back(TranslatableIntegerSettingOption(16314, VS_SCALINGMETHOD_CUBIC_MITCHELL)); + entries.push_back(TranslatableIntegerSettingOption(16321, VS_SCALINGMETHOD_CUBIC_CATMULL)); + entries.push_back(TranslatableIntegerSettingOption(16326, VS_SCALINGMETHOD_CUBIC_0_075)); + entries.push_back(TranslatableIntegerSettingOption(16330, VS_SCALINGMETHOD_CUBIC_0_1)); + entries.push_back(TranslatableIntegerSettingOption(16304, VS_SCALINGMETHOD_LANCZOS2)); + entries.push_back(TranslatableIntegerSettingOption(16323, VS_SCALINGMETHOD_SPLINE36_FAST)); + entries.push_back(TranslatableIntegerSettingOption(16315, VS_SCALINGMETHOD_LANCZOS3_FAST)); + entries.push_back(TranslatableIntegerSettingOption(16322, VS_SCALINGMETHOD_SPLINE36)); + entries.push_back(TranslatableIntegerSettingOption(16305, VS_SCALINGMETHOD_LANCZOS3)); + entries.push_back(TranslatableIntegerSettingOption(16306, VS_SCALINGMETHOD_SINC8)); + entries.push_back(TranslatableIntegerSettingOption(16307, VS_SCALINGMETHOD_BICUBIC_SOFTWARE)); + entries.push_back(TranslatableIntegerSettingOption(16308, VS_SCALINGMETHOD_LANCZOS_SOFTWARE)); + entries.push_back(TranslatableIntegerSettingOption(16309, VS_SCALINGMETHOD_SINC_SOFTWARE)); + entries.push_back(TranslatableIntegerSettingOption(13120, VS_SCALINGMETHOD_VDPAU_HARDWARE)); + entries.push_back(TranslatableIntegerSettingOption(16319, VS_SCALINGMETHOD_DXVA_HARDWARE)); + entries.push_back(TranslatableIntegerSettingOption(16316, VS_SCALINGMETHOD_AUTO)); + + /* remove unsupported methods */ + for(TranslatableIntegerSettingOptions::iterator it = entries.begin(); it != entries.end(); ) + { + if (appPlayer->Supports(static_cast<ESCALINGMETHOD>(it->value))) + ++it; + else + it = entries.erase(it); + } + + AddSpinner(groupVideo, SETTING_VIDEO_SCALINGMETHOD, 16300, SettingLevel::Basic, static_cast<int>(videoSettings.m_ScalingMethod), entries); + + AddVideoStreams(groupVideoStream, SETTING_VIDEO_STREAM); + + if (appPlayer->Supports(RENDERFEATURE_STRETCH) || appPlayer->Supports(RENDERFEATURE_PIXEL_RATIO)) + { + AddList(groupVideo, SETTING_VIDEO_VIEW_MODE, 629, SettingLevel::Basic, videoSettings.m_ViewMode, CViewModeSettings::ViewModesFiller, 629); + } + if (appPlayer->Supports(RENDERFEATURE_ZOOM)) + AddSlider(groupVideo, SETTING_VIDEO_ZOOM, 216, SettingLevel::Basic, + videoSettings.m_CustomZoomAmount, "{:2.2f}", 0.5f, 0.01f, 2.0f, 216, usePopup); + if (appPlayer->Supports(RENDERFEATURE_VERTICAL_SHIFT)) + AddSlider(groupVideo, SETTING_VIDEO_VERTICAL_SHIFT, 225, SettingLevel::Basic, + videoSettings.m_CustomVerticalShift, "{:2.2f}", -2.0f, 0.01f, 2.0f, 225, usePopup); + if (appPlayer->Supports(RENDERFEATURE_PIXEL_RATIO)) + AddSlider(groupVideo, SETTING_VIDEO_PIXEL_RATIO, 217, SettingLevel::Basic, + videoSettings.m_CustomPixelRatio, "{:2.2f}", 0.5f, 0.01f, 2.0f, 217, usePopup); + + AddList(groupVideo, SETTING_VIDEO_ORIENTATION, 21843, SettingLevel::Basic, videoSettings.m_Orientation, CGUIDialogVideoSettings::VideoOrientationFiller, 21843); + + if (appPlayer->Supports(RENDERFEATURE_POSTPROCESS)) + AddToggle(groupVideo, SETTING_VIDEO_POSTPROCESS, 16400, SettingLevel::Basic, videoSettings.m_PostProcess); + if (appPlayer->Supports(RENDERFEATURE_BRIGHTNESS)) + AddPercentageSlider(groupVideo, SETTING_VIDEO_BRIGHTNESS, 464, SettingLevel::Basic, static_cast<int>(videoSettings.m_Brightness), 14047, 1, 464, usePopup); + if (appPlayer->Supports(RENDERFEATURE_CONTRAST)) + AddPercentageSlider(groupVideo, SETTING_VIDEO_CONTRAST, 465, SettingLevel::Basic, static_cast<int>(videoSettings.m_Contrast), 14047, 1, 465, usePopup); + if (appPlayer->Supports(RENDERFEATURE_GAMMA)) + AddPercentageSlider(groupVideo, SETTING_VIDEO_GAMMA, 466, SettingLevel::Basic, static_cast<int>(videoSettings.m_Gamma), 14047, 1, 466, usePopup); + if (appPlayer->Supports(RENDERFEATURE_NOISE)) + AddSlider(groupVideo, SETTING_VIDEO_VDPAU_NOISE, 16312, SettingLevel::Basic, + videoSettings.m_NoiseReduction, "{:2.2f}", 0.0f, 0.01f, 1.0f, 16312, usePopup); + if (appPlayer->Supports(RENDERFEATURE_SHARPNESS)) + AddSlider(groupVideo, SETTING_VIDEO_VDPAU_SHARPNESS, 16313, SettingLevel::Basic, + videoSettings.m_Sharpness, "{:2.2f}", -1.0f, 0.02f, 1.0f, 16313, usePopup); + if (appPlayer->Supports(RENDERFEATURE_NONLINSTRETCH)) + AddToggle(groupVideo, SETTING_VIDEO_NONLIN_STRETCH, 659, SettingLevel::Basic, videoSettings.m_CustomNonLinStretch); + + // tone mapping + if (appPlayer->Supports(RENDERFEATURE_TONEMAP)) + { + bool visible = !(CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CServiceBroker::GetWinSystem()->SETTING_WINSYSTEM_IS_HDR_DISPLAY) && + CServiceBroker::GetWinSystem()->IsHDRDisplay()); + entries.clear(); + entries.push_back(TranslatableIntegerSettingOption(36554, VS_TONEMAPMETHOD_OFF)); + entries.push_back(TranslatableIntegerSettingOption(36555, VS_TONEMAPMETHOD_REINHARD)); + entries.push_back(TranslatableIntegerSettingOption(36557, VS_TONEMAPMETHOD_ACES)); + entries.push_back(TranslatableIntegerSettingOption(36558, VS_TONEMAPMETHOD_HABLE)); + + AddSpinner(groupVideo, SETTING_VIDEO_TONEMAP_METHOD, 36553, SettingLevel::Basic, + videoSettings.m_ToneMapMethod, entries, false, visible); + AddSlider(groupVideo, SETTING_VIDEO_TONEMAP_PARAM, 36556, SettingLevel::Basic, + videoSettings.m_ToneMapParam, "{:2.2f}", 0.1f, 0.1f, 5.0f, 36556, usePopup, false, + visible); + } + + // stereoscopic settings + entries.clear(); + entries.push_back(TranslatableIntegerSettingOption(16316, RENDER_STEREO_MODE_OFF)); + entries.push_back(TranslatableIntegerSettingOption(36503, RENDER_STEREO_MODE_SPLIT_HORIZONTAL)); + entries.push_back(TranslatableIntegerSettingOption(36504, RENDER_STEREO_MODE_SPLIT_VERTICAL)); + AddSpinner(groupStereoscopic, SETTING_VIDEO_STEREOSCOPICMODE, 36535, SettingLevel::Basic, videoSettings.m_StereoMode, entries); + AddToggle(groupStereoscopic, SETTING_VIDEO_STEREOSCOPICINVERT, 36536, SettingLevel::Basic, videoSettings.m_StereoInvert); + + // general settings + AddButton(groupSaveAsDefault, SETTING_VIDEO_MAKE_DEFAULT, 12376, SettingLevel::Basic); + AddButton(groupSaveAsDefault, SETTING_VIDEO_CALIBRATION, 214, SettingLevel::Basic); +} + +void CGUIDialogVideoSettings::AddVideoStreams(const std::shared_ptr<CSettingGroup>& group, + const std::string& settingId) +{ + if (group == NULL || settingId.empty()) + return; + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + m_videoStream = appPlayer->GetVideoStream(); + if (m_videoStream < 0) + m_videoStream = 0; + + AddList(group, settingId, 38031, SettingLevel::Basic, m_videoStream, VideoStreamsOptionFiller, 38031); +} + +void CGUIDialogVideoSettings::VideoStreamsOptionFiller( + const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + int videoStreamCount = appPlayer->GetVideoStreamCount(); + // cycle through each video stream and add it to our list control + for (int i = 0; i < videoStreamCount; ++i) + { + std::string strItem; + std::string strLanguage; + + VideoStreamInfo info; + appPlayer->GetVideoStreamInfo(i, info); + + g_LangCodeExpander.Lookup(info.language, strLanguage); + + if (!info.name.empty()) + { + if (!strLanguage.empty()) + strItem = StringUtils::Format("{} - {}", strLanguage, info.name); + else + strItem = info.name; + } + else if (!strLanguage.empty()) + { + strItem = strLanguage; + } + + if (info.codecName.empty()) + strItem += StringUtils::Format(" ({}x{}", info.width, info.height); + else + strItem += StringUtils::Format(" ({}, {}x{}", info.codecName, info.width, info.height); + + if (info.bitrate) + strItem += StringUtils::Format(", {} bps)", info.bitrate); + else + strItem += ")"; + + strItem += FormatFlags(info.flags); + strItem += StringUtils::Format(" ({}/{})", i + 1, videoStreamCount); + list.emplace_back(strItem, i); + } + + if (list.empty()) + { + list.emplace_back(g_localizeStrings.Get(231), -1); + current = -1; + } +} + +void CGUIDialogVideoSettings::VideoOrientationFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data) +{ + list.emplace_back(g_localizeStrings.Get(687), 0); + list.emplace_back(g_localizeStrings.Get(35229), 90); + list.emplace_back(g_localizeStrings.Get(35230), 180); + list.emplace_back(g_localizeStrings.Get(35231), 270); +} + +std::string CGUIDialogVideoSettings::FormatFlags(StreamFlags flags) +{ + std::vector<std::string> localizedFlags; + if (flags & StreamFlags::FLAG_DEFAULT) + localizedFlags.emplace_back(g_localizeStrings.Get(39105)); + if (flags & StreamFlags::FLAG_FORCED) + localizedFlags.emplace_back(g_localizeStrings.Get(39106)); + if (flags & StreamFlags::FLAG_HEARING_IMPAIRED) + localizedFlags.emplace_back(g_localizeStrings.Get(39107)); + if (flags & StreamFlags::FLAG_VISUAL_IMPAIRED) + localizedFlags.emplace_back(g_localizeStrings.Get(39108)); + + std::string formated = StringUtils::Join(localizedFlags, ", "); + + if (!formated.empty()) + formated = StringUtils::Format(" [{}]", formated); + + return formated; +} diff --git a/xbmc/video/dialogs/GUIDialogVideoSettings.h b/xbmc/video/dialogs/GUIDialogVideoSettings.h new file mode 100644 index 0000000..5314e40 --- /dev/null +++ b/xbmc/video/dialogs/GUIDialogVideoSettings.h @@ -0,0 +1,55 @@ +/* + * 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. + */ + +#pragma once + +#include "cores/VideoPlayer/Interface/StreamInfo.h" +#include "settings/dialogs/GUIDialogSettingsManualBase.h" + +#include <string> +#include <utility> +#include <vector> + +struct IntegerSettingOption; + +class CGUIDialogVideoSettings : public CGUIDialogSettingsManualBase +{ +public: + CGUIDialogVideoSettings(); + ~CGUIDialogVideoSettings() override; + +protected: + // implementations of ISettingCallback + void OnSettingChanged(const std::shared_ptr<const CSetting>& setting) override; + void OnSettingAction(const std::shared_ptr<const CSetting>& setting) override; + + void AddVideoStreams(const std::shared_ptr<CSettingGroup>& group, const std::string& settingId); + static void VideoStreamsOptionFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + + static void VideoOrientationFiller(const std::shared_ptr<const CSetting>& setting, + std::vector<IntegerSettingOption>& list, + int& current, + void* data); + + static std::string FormatFlags(StreamFlags flags); + + // specialization of CGUIDialogSettingsBase + bool AllowResettingSettings() const override { return false; } + bool Save() override; + void SetupView() override; + + // specialization of CGUIDialogSettingsManualBase + void InitializeSettings() override; + +private: + int m_videoStream; + bool m_viewModeChanged = false; +}; |